001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.j2me.payment;
028:
029: import java.io.*;
030: import java.util.Hashtable;
031: import java.util.Random;
032: import java.util.Vector;
033: import javax.microedition.io.Connector;
034:
035: import com.sun.midp.io.j2me.storage.*;
036:
037: import javax.microedition.payment.TransactionRecord;
038:
039: import com.sun.j2me.payment.PaymentInfo;
040: import com.sun.j2me.payment.ProviderInfo;
041: import com.sun.j2me.payment.Transaction;
042: import com.sun.j2me.payment.TransactionStore;
043:
044: import com.sun.midp.midletsuite.*;
045: import com.sun.midp.security.*;
046: import javax.microedition.rms.*;
047:
048: /**
049: * This class implements the transaction store. It uses RMS
050: * to store the transaction records.
051: *
052: */
053: public final class CldcTransactionStoreImpl implements TransactionStore {
054:
055: /**
056: * This class has a different security domain than the MIDlet suite.
057: * The token is initialized with a value handed to class constructor.
058: */
059: private static SecurityToken securityToken;
060:
061: /** The list of Transaction Records with reserved and missed status */
062: private Vector inProcessRecords = new Vector();
063:
064: /**
065: * The reference to the property containing maximum number of passed
066: * transactions
067: */
068: private static final String TRANSACTIONS_LIMIT_PROPERTY = "payment.transactions.limit";
069: /** The maximum number of passed transactions */
070: private static final int DEFAULT_TRANSACTIONS_LIMIT = 16;
071:
072: /** The maximum number of missed transactions per application. */
073: static final int MISSED_TRANSACTIONS_LIMIT = 4;
074: /** The maximum total number of past transactions. */
075: static final int PASSED_TRANSACTIONS_LIMIT;
076:
077: /** The RMS record ID of the NextTransactionID counter */
078: private static final int NEXT_TRANSACTION_RECORD_ID = 1;
079: /** The RMS record ID of the NextApplicationD counter */
080: private static final int NEXT_APPLICATION_RECORD_ID = 2;
081:
082: /** The number of bytes in the int */
083: private static final int SIZE_OF_INT = 4;
084:
085: /** lock used to synchronize this Transaction Store */
086: Object tsLock;
087:
088: static {
089: // get the passed transactions limit from the system
090: int limit = DEFAULT_TRANSACTIONS_LIMIT;
091: String limitStr = System
092: .getProperty(TRANSACTIONS_LIMIT_PROPERTY);
093: if (limitStr != null) {
094: try {
095: int value = Integer.parseInt(limitStr);
096: if (value >= 0) {
097: limit = value;
098: }
099: } catch (NumberFormatException e) {
100: }
101: }
102:
103: PASSED_TRANSACTIONS_LIMIT = limit;
104: }
105:
106: /**
107: * Creates a new instance of <code>CldcTransactionStoreImpl</code>. It needs
108: * a security token to perform file system manipulations.
109: *
110: * @param securityToken the security token
111: * @throws IOException indicates a storage failure
112: */
113: public CldcTransactionStoreImpl(SecurityToken securityToken)
114: throws IOException {
115: this .securityToken = securityToken;
116: tsLock = new Object();
117: initStore();
118: }
119:
120: /**
121: * Adds the given transaction to the store. It returns a new transaction
122: * record for the transaction. This transaction record must have its
123: * <code>wasMissed</code> flag cleared.
124: *
125: * @param transaction the transaction
126: * @return the new transaction record
127: * @throws IOException indicates a storage failure
128: */
129: public TransactionRecord addTransaction(Transaction transaction)
130: throws IOException {
131: int transactionID = transaction.getTransactionID();
132: CldcTransactionRecordImpl record = findReservedRecord(transactionID);
133: if (record == null) {
134: throw new IllegalStateException(
135: "The transaction record hasn't " + "been reserved");
136: }
137:
138: record.setStatus(CldcTransactionRecordImpl.MISSED);
139: record.update(transaction);
140:
141: if (!record.isFake()) {
142:
143: try {
144: TransactionStorageImpl store = new TransactionStorageImpl(
145: securityToken, false);
146: try {
147: writeTransactionRecord(record, store, record
148: .getRecordID());
149: } finally {
150: store.closeStore();
151: }
152: } catch (RecordStoreException ex) {
153: throw new IOException("Storage Failure: "
154: + ex.getMessage());
155: }
156: }
157:
158: // store the transaction record as missed but return it as not missed
159: return record.createDuplicateRecord();
160: }
161:
162: /**
163: * It returns <code>true</code> if the <code>setDelivered</code> method
164: * was called for the given transaction ID.
165: *
166: * @param transactionID the transaction ID
167: * @return true if transaction with Transaction ID were delivered to user
168: * @throws IOException indicates a storage failure
169: */
170: public boolean wasDelivered(int transactionID) throws IOException {
171: CldcTransactionRecordImpl transactionRecord = findMissedRecord(transactionID);
172: return transactionRecord == null;
173: }
174:
175: /**
176: * This method is called after the application is successfully notified
177: * about the transaction with the given transaction ID.
178: *
179: * @param transactionID the transaction ID
180: * @throws IOException indicates a storage failure
181: */
182: public void setDelivered(int transactionID) throws IOException {
183: CldcTransactionRecordImpl transactionRecord = null;
184:
185: synchronized (tsLock) {
186: transactionRecord = findMissedRecord(transactionID);
187: if (transactionRecord == null) {
188: throw new IllegalArgumentException(
189: "Not a stored transaction");
190: }
191:
192: inProcessRecords.removeElement(transactionRecord);
193: }
194:
195: if (!transactionRecord.isFake()) {
196: transactionRecord
197: .setStatus(CldcTransactionRecordImpl.PASSED);
198:
199: try {
200: TransactionStorageImpl store = new TransactionStorageImpl(
201: securityToken, false);
202: try {
203: if (transactionRecord.getRecordID() > 0) {
204: writeTransactionRecord(transactionRecord,
205: store, transactionRecord.getRecordID());
206: }
207: // limit the passed transactions count
208: limitPassedRecords(store);
209: } finally {
210: store.closeStore();
211: }
212: } catch (RecordStoreException ex) {
213: throw new IOException("Storage Failure "
214: + ex.getMessage());
215: }
216: }
217: }
218:
219: /**
220: * Returns an identification number, which can be used as
221: * <code>applicationID</code> in the other methods. During installation
222: * each payment supporting <code>MIDletSuite</code> should get such number
223: * and have it stored. From that point this number will identify that
224: * <code>MIDletSuite</code> to the transaction store.
225: *
226: * @return the payment application id
227: * @throws IOException indicates a storage failure
228: */
229: public int getNextApplicationID() throws IOException {
230: int nextApplicationID = 0;
231: try {
232: TransactionStorageImpl store = new TransactionStorageImpl(
233: securityToken, false);
234: try {
235: nextApplicationID = getIntFromByteArray(store
236: .getRecord(NEXT_APPLICATION_RECORD_ID));
237: nextApplicationID++;
238: if (nextApplicationID <= 0) {
239: nextApplicationID = 1;
240: }
241: store.setRecord(NEXT_APPLICATION_RECORD_ID,
242: getByteArrayFromInt(nextApplicationID));
243: } finally {
244: store.closeStore();
245: }
246: } catch (RecordStoreException ex) {
247: throw new IOException("Storage Failure");
248: }
249: return nextApplicationID;
250: }
251:
252: /**
253: * Returns an array of the missed transaction records for the given
254: * application ID. The transaction records are returned in the order in
255: * which they have been added to the store. Each transaction record must
256: * have its <code>wasMissed</code> flag set.
257: *
258: * @param applicationID the application ID
259: * @return the array of the missed transaction records
260: * @throws IOException indicates a storage failure
261: */
262: public TransactionRecord[] getMissedTransactions(int applicationID)
263: throws IOException {
264: return getMissedTransactions(applicationID, false);
265: }
266:
267: /**
268: * Returns a Vector of passed or missed transaction records for the given
269: * application ID.
270: *
271: * @param applicationID the application ID
272: * @param wasMissed true if missed transactions are required
273: *
274: * @return the Vector of transaction records
275: * @throws IOException indicates a storage failure
276: */
277: private Vector getTransactions(int applicationID, boolean wasMissed)
278: throws IOException {
279: Vector records = new Vector();
280:
281: /* Get none-fake elements */
282: try {
283: TransactionStorageImpl store = new TransactionStorageImpl(
284: securityToken, false);
285: try {
286: /* read passed transactions */
287: int[] recordIDs = store.getRecordIDs();
288: CldcTransactionRecordImpl r;
289: for (int i = 0; i < recordIDs.length; i++) {
290: int recId = recordIDs[i];
291: r = readTransactionRecord(store, recId);
292: if (null != r
293: && (r.getApplicationID() == applicationID)
294: && (r.wasMissed() == wasMissed)) {
295: r.add2list(records);
296: }
297: }
298: } finally {
299: store.closeStore();
300: }
301: } catch (RecordStoreException ex) {
302: throw new IOException("Storage Failure: " + ex.getMessage());
303: }
304:
305: return records;
306: }
307:
308: /**
309: * Returns an array of the past transaction records for the given
310: * application ID. The transaction record are returned in the reverse order
311: * as they have been added to the store (most recent first). Each
312: * transaction record must have its <code>wasMissed</code> flag cleared.
313: *
314: * @param applicationID the application ID
315: * @return the array of the passed transaction records
316: * @throws IOException indicates a storage failure
317: */
318: public TransactionRecord[] getPassedTransactions(int applicationID)
319: throws IOException {
320:
321: Vector passedRecords = getTransactions(applicationID, false);
322:
323: int count = passedRecords.size();
324:
325: if (count == 0) {
326: return null;
327: }
328:
329: CldcTransactionRecordImpl[] appRecords = new CldcTransactionRecordImpl[count];
330: /* sort passedRecords and store in appRecords (reverse mode) */
331: for (int i = 0, j = (count - 1); i < count; i++, j--) {
332: appRecords[j] = (CldcTransactionRecordImpl) passedRecords
333: .elementAt(i);
334: }
335: passedRecords.removeAllElements();
336:
337: return appRecords;
338: }
339:
340: /**
341: * Reserves space for the given transaction in the store. It should be
342: * called before any call to the <code>addTransaction</code> method to
343: * ensure that the <code>addTransaction</code> method won't fail later
344: * (when it is inappropriate) due to full store. This method can apply some
345: * store policies, like enforcing a maximum number of missed transactions
346: * per <code>MIDletSuite</code>.
347: * <p>
348: * The <code>applicationID</code> identifies the application (MIDlet suite)
349: * to the transaction store. MIDlet suites which are run directly can use
350: * negative application IDs to avoid permanent storing of created
351: * transaction records.
352: *
353: * @param applicationID the application id
354: * @param transaction the transaction
355: * @return an unique ID created for the transaction
356: * @throws IOException indicates that the store is full or won't accept any
357: * further transaction records from that application
358: */
359: public int reserve(int applicationID, Transaction transaction)
360: throws IOException {
361:
362: /* assign transactionID */
363: int transactionID;
364: try {
365: TransactionStorageImpl store = new TransactionStorageImpl(
366: securityToken, false);
367: try {
368:
369: int count = 0;
370: /* count missed and reserved transactions */
371: CldcTransactionRecordImpl r;
372: for (int i = 0; i < inProcessRecords.size(); i++) {
373: r = (CldcTransactionRecordImpl) inProcessRecords
374: .elementAt(i);
375: if (r.wasMissed()
376: && (r.getApplicationID() == applicationID)) {
377: count++;
378: }
379: }
380:
381: /* check the limit */
382: if (count >= MISSED_TRANSACTIONS_LIMIT) {
383: throw new IOException("No more space for records");
384: }
385:
386: String applicationName = transaction
387: .getTransactionModule()
388: .getMIDlet()
389: .getAppProperty(MIDletSuiteImpl.SUITE_NAME_PROP);
390:
391: /* assign transactionID */
392: int nextTransactionID = getIntFromByteArray(store
393: .getRecord(NEXT_TRANSACTION_RECORD_ID));
394: transactionID = nextTransactionID++;
395: if (nextTransactionID <= 0) {
396: nextTransactionID = 1;
397: }
398: transaction.setTransactionID(transactionID);
399:
400: /* don't reserve space if applicationID < 0 */
401: if (applicationID > 0) {
402: /* create reserved transaction */
403: CldcTransactionRecordImpl newTransactionRecord = new CldcTransactionRecordImpl(
404: applicationID, applicationName,
405: transaction, System.currentTimeMillis(),
406: false);
407:
408: store.setRecord(NEXT_TRANSACTION_RECORD_ID,
409: getByteArrayFromInt(nextTransactionID));
410:
411: writeTransactionRecord(newTransactionRecord, store,
412: 0);
413:
414: inProcessRecords.addElement(newTransactionRecord);
415: } else {
416: /* create reserved fake transaction */
417: CldcTransactionRecordImpl newTransactionRecord = new CldcTransactionRecordImpl(
418: applicationID, applicationName,
419: transaction, System.currentTimeMillis(),
420: true);
421: inProcessRecords.addElement(newTransactionRecord);
422: }
423: } finally {
424: store.closeStore();
425: }
426: } catch (RecordStoreException ex) {
427: throw new IOException("Storage Failure: " + ex.getMessage());
428: }
429: return transactionID;
430: }
431:
432: /**
433: * Returns the size which is used in the store by the application of the
434: * given application ID. This size doesn't include the size of the passed
435: * transactions (it includes only the part of the store which is
436: * removed/uninstalled by the <code>removeApplicationRecords</code> method).
437: *
438: * @param applicationID the application ID
439: * @return the size used by the application
440: * @throws IOException indicates a storage failure
441: */
442: public int getSizeUsedByApplication(int applicationID)
443: throws IOException {
444:
445: int size = 0;
446: try {
447: TransactionStorageImpl store = new TransactionStorageImpl(
448: securityToken, false);
449: try {
450: /* read missed transactions */
451: int[] recordIDs = store.getRecordIDs();
452: CldcTransactionRecordImpl r;
453: for (int i = 0; i < recordIDs.length; i++) {
454: int recId = recordIDs[i];
455: r = readTransactionRecord(store, recId);
456: if (null != r
457: && (r.getApplicationID() == applicationID)
458: && r.wasMissed()) {
459: size += store.getRecordSize(recId);
460: }
461: }
462: } finally {
463: store.closeStore();
464: }
465: }
466:
467: catch (RecordStoreException ex) {
468: throw new IOException("Storage Failure: " + ex.getMessage());
469: }
470:
471: return size;
472: }
473:
474: /**
475: * Removes the missed records used by the application of the given
476: * application ID. This is to be used, when the MIDlet suite is uninstalled.
477: *
478: * @param applicationID the application ID
479: * @throws IOException indicates a storage failure
480: */
481: public void removeApplicationRecords(int applicationID)
482: throws IOException {
483: try {
484: TransactionStorageImpl store = new TransactionStorageImpl(
485: securityToken, false);
486: try {
487: /* read missed and passed transactions */
488: int[] recordIDs = store.getRecordIDs();
489: CldcTransactionRecordImpl r;
490: for (int i = 0; i < recordIDs.length; i++) {
491: int recId = recordIDs[i];
492: r = readTransactionRecord(store, recId);
493: if (null != r
494: && (r.getApplicationID() == applicationID)
495: && r.wasMissed()) {
496: store.deleteRecord(recId);
497: }
498: }
499: } finally {
500: store.closeStore();
501: }
502: } catch (RecordStoreException ex) {
503: throw new IOException("Storage Failure: " + ex.getMessage());
504: }
505:
506: synchronized (tsLock) {
507: /* count missed and reserved transactions */
508: CldcTransactionRecordImpl r;
509: for (int i = (inProcessRecords.size() - 1); i >= 0; i--) {
510: r = (CldcTransactionRecordImpl) inProcessRecords
511: .elementAt(i);
512: if (r.wasMissed()
513: && (r.getApplicationID() == applicationID)) {
514: inProcessRecords.removeElementAt(i);
515: }
516: }
517: }
518: }
519:
520: /**
521: * Removes all transaction records from the store. This is a helper method
522: * which is used in test suites to get clean state before test execution.
523: *
524: * @throws IOException indicates a storage failure
525: */
526: public void cleanUp() throws IOException {
527: try {
528: TransactionStorageImpl.deleteStore(securityToken);
529: } catch (RecordStoreException ex) {
530: // Nothing to do
531: }
532:
533: initStore();
534: }
535:
536: /**
537: * Returns an array of the missed transaction records for the given
538: * application ID. The transaction records are returned in the order in
539: * which they have been added to the store. Each transaction record must
540: * have its <code>wasMissed</code> flag set.
541: *
542: * @param applicationID the application ID
543: * @param fakeOnly if true returns only fake transactions for given
544: * applicationID
545: * @return the array of the missed transaction records
546: * @throws IOException indicates a storage failure
547: */
548: TransactionRecord[] getMissedTransactions(int applicationID,
549: boolean fakeOnly) throws IOException {
550: Vector missedRecords = new Vector();
551: if (!fakeOnly) {
552: missedRecords = getTransactions(applicationID, true);
553: }
554:
555: synchronized (tsLock) {
556: /* Get FakeOnly elements */
557: for (int i = 0; i < inProcessRecords.size(); ++i) {
558: CldcTransactionRecordImpl record = (CldcTransactionRecordImpl) inProcessRecords
559: .elementAt(i);
560: if ((record.getApplicationID() == applicationID)
561: && record.wasMissed() && record.isFake()) {
562: record.add2list(missedRecords);
563: }
564: }
565: }
566:
567: int count = missedRecords.size();
568:
569: if (count == 0) {
570: return null;
571: }
572:
573: CldcTransactionRecordImpl[] appRecords = new CldcTransactionRecordImpl[count];
574:
575: missedRecords.copyInto(appRecords);
576:
577: return appRecords;
578: }
579:
580: // === DEBUG MODE ===
581: /**
582: * Generates Fake records for Debug mode
583: *
584: * @param applicationID the application ID
585: * @param applicationName the application Name
586: * @param paymentInfo properties of the payment module
587: * @param featurePrefix prefix of the feature
588: * @param validProviders array of Provider IDs
589: * @param count number of fake transactions to add
590: * @throws IOException indicates a storage failure
591: */
592: void generateFakeRecords(int applicationID, String applicationName,
593: PaymentInfo paymentInfo, String featurePrefix,
594: int[] validProviders, int count) throws IOException {
595: // count > 0
596:
597: // reserve transaction IDs
598: int transactionID;
599:
600: try {
601: TransactionStorageImpl store = new TransactionStorageImpl(
602: securityToken, false);
603: try {
604: /* assign transactionID */
605: int nextTransactionID = getIntFromByteArray(store
606: .getRecord(NEXT_TRANSACTION_RECORD_ID));
607: transactionID = nextTransactionID;
608: nextTransactionID += count;
609: if (nextTransactionID <= 0) {
610: nextTransactionID = count;
611: }
612:
613: store.setRecord(NEXT_TRANSACTION_RECORD_ID,
614: getByteArrayFromInt(nextTransactionID));
615: } finally {
616: store.closeStore();
617: }
618: } catch (RecordStoreException ex) {
619: throw new IOException("Storage Failure: " + ex.getMessage());
620: }
621:
622: Random random = new Random();
623:
624: long timestamp = System.currentTimeMillis() - 3600000;
625: int deltaTime = 3600000 / count;
626: int numFeatures = paymentInfo.getNumFeatures();
627:
628: for (int i = 0; i < count; ++i) {
629: int state = random.nextInt(3);
630: int featureID = (i + (count >>> 1)) * (numFeatures - 1)
631: / count;
632: double price;
633: String currency;
634:
635: if (state != TransactionRecord.TRANSACTION_REJECTED) {
636: int priceTag = paymentInfo
637: .getPriceTagForFeature(featureID);
638: int providerID = validProviders[random
639: .nextInt(validProviders.length)];
640: ProviderInfo providerInfo = paymentInfo
641: .getProvider(providerID);
642:
643: price = providerInfo.getPrice(priceTag);
644: currency = providerInfo.getCurrency();
645: } else {
646: price = 0;
647: currency = "";
648: }
649:
650: inProcessRecords.addElement(CldcTransactionRecordImpl
651: .createFakeRecord(transactionID++, applicationID,
652: applicationName, featureID, featurePrefix
653: + featureID, price, currency,
654: state, timestamp));
655:
656: if (transactionID <= 0) {
657: transactionID = 1;
658: }
659:
660: timestamp += deltaTime;
661: }
662: }
663:
664: // === DEBUG MODE ===
665:
666: /**
667: * Finds Transaction Record with given Transaction ID and status
668: *
669: * @param transactionID the transaction ID
670: * @param status the status of transaction
671: * @return the transaction record if found or null
672: * @throws IOException indicates a storage failure
673: */
674: private CldcTransactionRecordImpl findRecord(int transactionID,
675: int status) throws IOException {
676:
677: CldcTransactionRecordImpl r = null;
678: CldcTransactionRecordImpl record = null;
679:
680: synchronized (tsLock) {
681: int count = inProcessRecords.size();
682: /* Try to find in the vector */
683: for (int i = 0; i < count; ++i) {
684: r = (CldcTransactionRecordImpl) inProcessRecords
685: .elementAt(i);
686: if (r.getTransactionID() == transactionID) {
687: return r;
688: }
689: }
690: }
691: /* Get none-fake elements */
692: try {
693: TransactionStorageImpl store = new TransactionStorageImpl(
694: securityToken, false);
695: try {
696: /* fiind reserved transaction */
697: int[] recordIDs = store.getRecordIDs();
698: for (int i = 0; i < recordIDs.length; i++) {
699: int recId = recordIDs[i];
700: r = readTransactionRecord(store, recId);
701: if (null != r
702: && (r.getTransactionID() == transactionID)
703: && (r.getStatus() == status)) {
704: r.setRecordID(recId);
705: record = r;
706: break;
707: }
708: }
709: } finally {
710: store.closeStore();
711: }
712: } catch (RecordStoreException ex) {
713: throw new IOException("Storage Failure: " + ex.getMessage());
714: } catch (IOException ex) {
715: throw new IOException("Storage Failure: " + ex.getMessage());
716: }
717:
718: return record;
719: }
720:
721: /**
722: * Finds missed Transaction Record with the given Transaction ID
723: *
724: * @param transactionID the transaction ID
725: * @return the transaction record if found or null
726: * @throws IOException indicates a storage failure
727: */
728: private CldcTransactionRecordImpl findMissedRecord(int transactionID)
729: throws IOException {
730: return findRecord(transactionID,
731: CldcTransactionRecordImpl.MISSED);
732: }
733:
734: /**
735: * Finds reserved Transaction Record with the given Transaction ID
736: *
737: * @param transactionID the transaction ID
738: * @return the transaction record if found or null
739: * @throws IOException indicates a storage failure
740: */
741: private CldcTransactionRecordImpl findReservedRecord(
742: int transactionID) throws IOException {
743: return findRecord(transactionID,
744: CldcTransactionRecordImpl.RESERVED);
745: }
746:
747: /**
748: * retrives Int from the ByteArray
749: *
750: * @param data the array of bytes
751: * @return integer value converted from byte array or -1
752: */
753: static int getIntFromByteArray(byte[] data) {
754: if (data.length == SIZE_OF_INT) {
755: return (((int) (data[0]) << 24) | ((int) (data[1]) << 16)
756: | ((int) (data[2]) << 8) | (int) (data[3]));
757: }
758: return -1;
759: }
760:
761: /**
762: * puts integer into the ByteArray
763: *
764: * @param v the integer value
765: * @return byte array containing the integer value
766: */
767: static byte[] getByteArrayFromInt(int v) {
768: byte[] data = new byte[SIZE_OF_INT];
769:
770: data[0] = (byte) ((byte) ((v) >> 24) & 0xFF);
771: data[1] = (byte) ((byte) ((v) >> 16) & 0xFF);
772: data[2] = (byte) ((byte) ((v) >> 8) & 0xFF);
773: data[3] = (byte) ((byte) (v) & 0xFF);
774:
775: return data;
776: }
777:
778: /**
779: * Initializes the store. It's used when the transaction store file doesn't
780: * exists.
781: *
782: * @throws IOException indicates a storage failure
783: */
784: private void initStore() throws IOException {
785: int nextTransactionID = 1;
786: int nextApplicationID = 1;
787:
788: try {
789: TransactionStorageImpl store = new TransactionStorageImpl(
790: securityToken, true);
791: try {
792: if (store.getNumRecords() == 0) {
793: /* Record ID = NEXT_TRANSACTION_RECORD_ID */
794: store
795: .addRecord(getByteArrayFromInt(nextTransactionID));
796: /* Record ID = NEXT_APPLICATION_RECORD_ID */
797: store
798: .addRecord(getByteArrayFromInt(nextApplicationID));
799: }
800: } catch (RecordStoreException ex) {
801: throw new IOException(
802: "Error initializing Transaction Store: "
803: + ex.getMessage());
804: } finally {
805: store.closeStore();
806: }
807: } catch (RecordStoreFullException ex) {
808: throw new IOException(
809: "Error initializing Transaction Store: "
810: + ex.getMessage());
811: } catch (RecordStoreException ex) {
812: /* Nothing to do. The store is creating by another isolate */
813: }
814: }
815:
816: /**
817: * Limits the count of the passed transaction records in the given
818: * vector to the given value.
819: *
820: * @param st TransactionStorageImpl reference
821: *
822: * @throws RecordStoreException indicates a storage failure
823: * @throws IOException indicates a storage failure
824: */
825: private void limitPassedRecords(TransactionStorageImpl st)
826: throws RecordStoreException, IOException {
827: Vector passedRecords = new Vector();
828: CldcTransactionRecordImpl r;
829: /* read passed transactions */
830: int[] recordIDs = st.getRecordIDs();
831: for (int i = 0; i < recordIDs.length; i++) {
832: int recId = recordIDs[i];
833: r = readTransactionRecord(st, recId);
834: if (null != r && !r.wasMissed()) {
835: r.add2list(passedRecords);
836: }
837: }
838:
839: while (passedRecords.size() > PASSED_TRANSACTIONS_LIMIT) {
840: r = (CldcTransactionRecordImpl) passedRecords.elementAt(0);
841: st.deleteRecord(r.getRecordID());
842: passedRecords.removeElementAt(0);
843: }
844: }
845:
846: /**
847: * Remove missed transaction for give application.
848: *
849: * @param appID application payment id
850: * @throws IOException if there is an error accessing storage
851: */
852: protected void removeMissedTransaction(int appID)
853: throws IOException {
854: CldcTransactionRecordImpl[] recs = (CldcTransactionRecordImpl[]) getMissedTransactions(appID);
855: if (null == recs) {
856: return;
857: }
858: try {
859: TransactionStorageImpl store = new TransactionStorageImpl(
860: securityToken, false);
861: try {
862: for (int i = 0; i < recs.length; i++) {
863: store.deleteRecord(recs[i].getRecordID());
864: }
865: } finally {
866: store.closeStore();
867: }
868: } catch (RecordStoreException ex) {
869: throw new IOException("Storage Failure: " + ex.getMessage());
870: }
871: }
872:
873: /**
874: * This service function reads and returns TransactionRecord from store.
875: *
876: * @param st TransactionStorageImpl reference
877: * @param recId ID of the record to be read
878: * @return CldcTransactionRecordImpl if recId points to proper record,
879: * otherwise null
880: * @throws IOException if an I/O error occurs
881: * @throws RecordStoreException if a general record store exception occurs
882: */
883: private CldcTransactionRecordImpl readTransactionRecord(
884: TransactionStorageImpl st, int recId) throws IOException,
885: RecordStoreException {
886: byte[] nextRec = st.getRecord(recId);
887: CldcTransactionRecordImpl r = null;
888: if (nextRec.length > SIZE_OF_INT) {
889: ByteArrayInputStream bis = new ByteArrayInputStream(nextRec);
890: DataInputStream is = new DataInputStream(bis);
891: r = CldcTransactionRecordImpl.read(is);
892: r.setRecordID(recId);
893: }
894: return r;
895: }
896:
897: /**
898: * This service function writes TransactionRecord into store.
899: *
900: * @param record CldcTransactionRecordImpl reference
901: * @param st TransactionStorageImpl reference
902: * @param recId ID of the record to be replaced or 0
903: *
904: * @throws IOException if an I/O error occurs
905: * @throws RecordStoreException if a general record store exception occurs
906: */
907: private void writeTransactionRecord(
908: CldcTransactionRecordImpl record,
909: TransactionStorageImpl st, int recId) throws IOException,
910: RecordStoreException {
911:
912: /* formate transaction inside ByteArray */
913: ByteArrayOutputStream bos = new ByteArrayOutputStream();
914: DataOutputStream os = new DataOutputStream(bos);
915: record.write(os);
916: byte[] data = bos.toByteArray();
917:
918: if (recId == 0) {
919: recId = st.addRecord(data);
920: record.setRecordID(recId);
921: } else {
922: st.setRecord(recId, data);
923: }
924: }
925: }
|