FileDocCategorySizeDatePackage
CldcTransactionStoreImpl.javaAPI DocphoneME MR2 API (J2ME)33972Wed May 02 18:00:44 BST 2007com.sun.j2me.payment

CldcTransactionStoreImpl

public final class CldcTransactionStoreImpl extends Object implements com.sun.j2me.payment.TransactionStore
This class implements the transaction store. It uses RMS to store the transaction records.

Fields Summary
private static SecurityToken
securityToken
This class has a different security domain than the MIDlet suite. The token is initialized with a value handed to class constructor.
private Vector
inProcessRecords
The list of Transaction Records with reserved and missed status
private static final String
TRANSACTIONS_LIMIT_PROPERTY
The reference to the property containing maximum number of passed transactions
private static final int
DEFAULT_TRANSACTIONS_LIMIT
The maximum number of passed transactions
static final int
MISSED_TRANSACTIONS_LIMIT
The maximum number of missed transactions per application.
static final int
PASSED_TRANSACTIONS_LIMIT
The maximum total number of past transactions.
private static final int
NEXT_TRANSACTION_RECORD_ID
The RMS record ID of the NextTransactionID counter
private static final int
NEXT_APPLICATION_RECORD_ID
The RMS record ID of the NextApplicationD counter
private static final int
SIZE_OF_INT
The number of bytes in the int
Object
tsLock
lock used to synchronize this Transaction Store
Constructors Summary
public CldcTransactionStoreImpl(SecurityToken securityToken)
Creates a new instance of CldcTransactionStoreImpl. It needs a security token to perform file system manipulations.

param
securityToken the security token
throws
IOException indicates a storage failure

    
     
        // get the passed transactions limit from the system
        int limit = DEFAULT_TRANSACTIONS_LIMIT;
        String limitStr = System.getProperty(TRANSACTIONS_LIMIT_PROPERTY);
        if (limitStr != null) {
            try {
                int value = Integer.parseInt(limitStr);
                if (value >= 0) {
                    limit = value;
                }
            } catch (NumberFormatException e) {
            }
        }

        PASSED_TRANSACTIONS_LIMIT = limit;
    
        this.securityToken = securityToken;
        tsLock = new Object();
        initStore();
    
Methods Summary
public javax.microedition.payment.TransactionRecordaddTransaction(com.sun.j2me.payment.Transaction transaction)
Adds the given transaction to the store. It returns a new transaction record for the transaction. This transaction record must have its wasMissed flag cleared.

param
transaction the transaction
return
the new transaction record
throws
IOException indicates a storage failure

        int transactionID = transaction.getTransactionID();
        CldcTransactionRecordImpl record = findReservedRecord(transactionID);
        if (record == null) {
            throw new IllegalStateException("The transaction record hasn't " +
                                            "been reserved");
        }

        record.setStatus(CldcTransactionRecordImpl.MISSED);
        record.update(transaction);

        if (!record.isFake()) {
            
            try {
                TransactionStorageImpl store = 
                        new TransactionStorageImpl(securityToken, false);
                try {
                    writeTransactionRecord(record, store, record.getRecordID());
                } finally {
                    store.closeStore();
                }
            } catch (RecordStoreException ex) {
                throw new IOException("Storage Failure: " + ex.getMessage());
            }
        }

        // store the transaction record as missed but return it as not missed
        return record.createDuplicateRecord();
    
public voidcleanUp()
Removes all transaction records from the store. This is a helper method which is used in test suites to get clean state before test execution.

throws
IOException indicates a storage failure

        try {
            TransactionStorageImpl.deleteStore(securityToken);
        } catch (RecordStoreException ex) {
            // Nothing to do
        }

        initStore();
    
private CldcTransactionRecordImplfindMissedRecord(int transactionID)
Finds missed Transaction Record with the given Transaction ID

param
transactionID the transaction ID
return
the transaction record if found or null
throws
IOException indicates a storage failure

        return findRecord(transactionID, CldcTransactionRecordImpl.MISSED);
    
private CldcTransactionRecordImplfindRecord(int transactionID, int status)
Finds Transaction Record with given Transaction ID and status

param
transactionID the transaction ID
param
status the status of transaction
return
the transaction record if found or null
throws
IOException indicates a storage failure


        CldcTransactionRecordImpl r = null;
        CldcTransactionRecordImpl record = null;

        synchronized (tsLock) {
            int count = inProcessRecords.size();
            /* Try to find in the vector */
            for (int i = 0; i < count; ++i) {
                r = (CldcTransactionRecordImpl)
                    inProcessRecords.elementAt(i);
                if (r.getTransactionID() == transactionID) {
                    return r;
                }
            }
        }
        /* Get none-fake elements */
        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                /* fiind reserved transaction */
                int[] recordIDs = store.getRecordIDs();
                for (int i = 0; i < recordIDs.length; i++) {
                    int recId = recordIDs[i];
                    r = readTransactionRecord(store, recId);
                    if (null != r &&
                        (r.getTransactionID() == transactionID) &&
                        (r.getStatus() == status)) {
                        r.setRecordID(recId);
                        record = r;
                        break;
                    }
                }
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        } catch (IOException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        }

        return record;
    
private CldcTransactionRecordImplfindReservedRecord(int transactionID)
Finds reserved Transaction Record with the given Transaction ID

param
transactionID the transaction ID
return
the transaction record if found or null
throws
IOException indicates a storage failure

        return findRecord(transactionID, CldcTransactionRecordImpl.RESERVED);
    
voidgenerateFakeRecords(int applicationID, java.lang.String applicationName, com.sun.j2me.payment.PaymentInfo paymentInfo, java.lang.String featurePrefix, int[] validProviders, int count)
Generates Fake records for Debug mode

param
applicationID the application ID
param
applicationName the application Name
param
paymentInfo properties of the payment module
param
featurePrefix prefix of the feature
param
validProviders array of Provider IDs
param
count number of fake transactions to add
throws
IOException indicates a storage failure

        // count > 0

        // reserve transaction IDs
        int transactionID;

        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                /* assign transactionID */
                int nextTransactionID = getIntFromByteArray(
                    store.getRecord(NEXT_TRANSACTION_RECORD_ID));
                transactionID = nextTransactionID;
                nextTransactionID += count;
                if (nextTransactionID <= 0) {
                    nextTransactionID = count;
                }

                store.setRecord(NEXT_TRANSACTION_RECORD_ID,
                      getByteArrayFromInt(nextTransactionID));
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        }

        Random random = new Random();

        long timestamp = System.currentTimeMillis() - 3600000;
        int deltaTime = 3600000 / count;
        int numFeatures = paymentInfo.getNumFeatures();

        for (int i = 0; i < count; ++i) {
            int state = random.nextInt(3);
            int featureID = (i + (count >>> 1)) * (numFeatures - 1) / count;
            double price;
            String currency;

            if (state != TransactionRecord.TRANSACTION_REJECTED) {
                int priceTag = paymentInfo.getPriceTagForFeature(featureID);
                int providerID = validProviders[
                                 random.nextInt(validProviders.length)];
                ProviderInfo providerInfo = paymentInfo.getProvider(providerID);

                price = providerInfo.getPrice(priceTag);
                currency = providerInfo.getCurrency();
            } else {
                price = 0;
                currency = "";
            }

            inProcessRecords.addElement(
                CldcTransactionRecordImpl.createFakeRecord(
                    transactionID++, applicationID, applicationName,
                    featureID, featurePrefix + featureID, price, currency,
                    state, timestamp));

            if (transactionID <= 0) {
                transactionID = 1;
            }

            timestamp += deltaTime;
        }
    
static byte[]getByteArrayFromInt(int v)
puts integer into the ByteArray

param
v the integer value
return
byte array containing the integer value

        byte[] data = new byte[SIZE_OF_INT];

        data[0] = (byte) ((byte) ((v) >> 24) & 0xFF);
        data[1] = (byte) ((byte) ((v) >> 16) & 0xFF);
        data[2] = (byte) ((byte) ((v) >> 8) & 0xFF);
        data[3] = (byte) ((byte) (v) & 0xFF);

        return data;
    
static intgetIntFromByteArray(byte[] data)
retrives Int from the ByteArray

param
data the array of bytes
return
integer value converted from byte array or -1

        if (data.length == SIZE_OF_INT) {
            return (((int) (data[0]) << 24) |
                    ((int) (data[1]) << 16) |
                    ((int) (data[2]) << 8) |
                    (int) (data[3]));
        }
        return -1;
    
javax.microedition.payment.TransactionRecord[]getMissedTransactions(int applicationID, boolean fakeOnly)
Returns an array of the missed transaction records for the given application ID. The transaction records are returned in the order in which they have been added to the store. Each transaction record must have its wasMissed flag set.

param
applicationID the application ID
param
fakeOnly if true returns only fake transactions for given applicationID
return
the array of the missed transaction records
throws
IOException indicates a storage failure

        Vector missedRecords = new Vector();
        if (!fakeOnly) {
            missedRecords = getTransactions(applicationID, true);
        }

        synchronized (tsLock) {
            /* Get FakeOnly elements */
            for (int i = 0; i < inProcessRecords.size(); ++i) {
                CldcTransactionRecordImpl record = 
                    (CldcTransactionRecordImpl)inProcessRecords.elementAt(i);
                if ((record.getApplicationID() == applicationID) &&
                    record.wasMissed() && record.isFake()) {
                    record.add2list(missedRecords);
                }
            }
        }
        
        int count = missedRecords.size();

        if (count == 0) {
            return null;
        }

        CldcTransactionRecordImpl[] appRecords =
            new CldcTransactionRecordImpl[count];
        
        missedRecords.copyInto(appRecords);

        return appRecords;
    
public javax.microedition.payment.TransactionRecord[]getMissedTransactions(int applicationID)
Returns an array of the missed transaction records for the given application ID. The transaction records are returned in the order in which they have been added to the store. Each transaction record must have its wasMissed flag set.

param
applicationID the application ID
return
the array of the missed transaction records
throws
IOException indicates a storage failure

        return getMissedTransactions(applicationID, false);
    
public intgetNextApplicationID()
Returns an identification number, which can be used as applicationID in the other methods. During installation each payment supporting MIDletSuite should get such number and have it stored. From that point this number will identify that MIDletSuite to the transaction store.

return
the payment application id
throws
IOException indicates a storage failure

        int nextApplicationID = 0;
        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                nextApplicationID = getIntFromByteArray(
                    store.getRecord(NEXT_APPLICATION_RECORD_ID));
                nextApplicationID++;
                if (nextApplicationID <= 0) {
                    nextApplicationID = 1;
                }
                store.setRecord(NEXT_APPLICATION_RECORD_ID,
                        getByteArrayFromInt(nextApplicationID));
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure");
        }
        return nextApplicationID;
    
public javax.microedition.payment.TransactionRecord[]getPassedTransactions(int applicationID)
Returns an array of the past transaction records for the given application ID. The transaction record are returned in the reverse order as they have been added to the store (most recent first). Each transaction record must have its wasMissed flag cleared.

param
applicationID the application ID
return
the array of the passed transaction records
throws
IOException indicates a storage failure


        Vector passedRecords = getTransactions(applicationID, false);

        int count = passedRecords.size();

        if (count == 0) {
            return null;
        }

        CldcTransactionRecordImpl[] appRecords =
            new CldcTransactionRecordImpl[count];
        /* sort passedRecords and store in appRecords (reverse mode) */
        for (int i = 0, j = (count - 1); i < count; i++, j--) {
            appRecords[j] = (CldcTransactionRecordImpl)
                            passedRecords.elementAt(i);
        }
        passedRecords.removeAllElements();

        return appRecords;
    
public intgetSizeUsedByApplication(int applicationID)
Returns the size which is used in the store by the application of the given application ID. This size doesn't include the size of the passed transactions (it includes only the part of the store which is removed/uninstalled by the removeApplicationRecords method).

param
applicationID the application ID
return
the size used by the application
throws
IOException indicates a storage failure


        int size = 0;
        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                /* read missed transactions */
                int[] recordIDs = store.getRecordIDs();
                CldcTransactionRecordImpl r;
                for (int i = 0; i < recordIDs.length; i++) {
                    int recId = recordIDs[i];
                    r = readTransactionRecord(store, recId);
                    if (null != r &&
                        (r.getApplicationID() == applicationID) &&
                        r.wasMissed()) {
                        size += store.getRecordSize(recId);
                    }
                }
            } finally {
                store.closeStore();
            }
        }

        catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        }

        return size;
    
private java.util.VectorgetTransactions(int applicationID, boolean wasMissed)
Returns a Vector of passed or missed transaction records for the given application ID.

param
applicationID the application ID
param
wasMissed true if missed transactions are required
return
the Vector of transaction records
throws
IOException indicates a storage failure

        Vector records = new Vector();

        /* Get none-fake elements */
        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                /* read passed transactions */
                int[] recordIDs = store.getRecordIDs();
                CldcTransactionRecordImpl r;
                for (int i = 0; i < recordIDs.length; i++) {
                    int recId = recordIDs[i];
                    r = readTransactionRecord(store, recId);
                    if (null != r &&
                        (r.getApplicationID() == applicationID) &&
                        (r.wasMissed() == wasMissed)) {
                        r.add2list(records);
                    }
                }
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        }

        return records;
    
private voidinitStore()
Initializes the store. It's used when the transaction store file doesn't exists.

throws
IOException indicates a storage failure

        int nextTransactionID = 1;
        int nextApplicationID = 1;

        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, true);
            try {
		if (store.getNumRecords() == 0) {
                    /* Record ID = NEXT_TRANSACTION_RECORD_ID */
                    store.addRecord(getByteArrayFromInt(nextTransactionID));
                    /* Record ID = NEXT_APPLICATION_RECORD_ID */
                    store.addRecord(getByteArrayFromInt(nextApplicationID));
                }
            } catch (RecordStoreException ex) {
                throw new IOException(
                    "Error initializing Transaction Store: " + ex.getMessage());
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreFullException ex) {
            throw new IOException("Error initializing Transaction Store: " + 
                    ex.getMessage());
        } catch (RecordStoreException ex) {
            /* Nothing to do. The store is creating by another isolate */
        }
    
private voidlimitPassedRecords(TransactionStorageImpl st)
Limits the count of the passed transaction records in the given vector to the given value.

param
st TransactionStorageImpl reference
throws
RecordStoreException indicates a storage failure
throws
IOException indicates a storage failure

        Vector passedRecords = new Vector();
        CldcTransactionRecordImpl r;
        /* read passed transactions */
        int[] recordIDs = st.getRecordIDs();
        for (int i = 0; i < recordIDs.length; i++) {
            int recId = recordIDs[i];
            r = readTransactionRecord(st, recId);
            if (null != r &&
                !r.wasMissed()) {
                r.add2list(passedRecords);
            }
        }

        while (passedRecords.size() > PASSED_TRANSACTIONS_LIMIT) {
            r = (CldcTransactionRecordImpl) passedRecords.elementAt(0);
            st.deleteRecord(r.getRecordID());
            passedRecords.removeElementAt(0);
        }
    
private CldcTransactionRecordImplreadTransactionRecord(TransactionStorageImpl st, int recId)
This service function reads and returns TransactionRecord from store.

param
st TransactionStorageImpl reference
param
recId ID of the record to be read
return
CldcTransactionRecordImpl if recId points to proper record, otherwise null
throws
IOException if an I/O error occurs
throws
RecordStoreException if a general record store exception occurs

        byte[] nextRec = st.getRecord(recId);
        CldcTransactionRecordImpl r = null;
        if (nextRec.length > SIZE_OF_INT) {
            ByteArrayInputStream bis =
                new ByteArrayInputStream(nextRec);
            DataInputStream is = new DataInputStream(bis);
            r = CldcTransactionRecordImpl.read(is);
            r.setRecordID(recId);
        }
        return r;
    
public voidremoveApplicationRecords(int applicationID)
Removes the missed records used by the application of the given application ID. This is to be used, when the MIDlet suite is uninstalled.

param
applicationID the application ID
throws
IOException indicates a storage failure

        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                /* read missed and passed transactions */
                int[] recordIDs = store.getRecordIDs();
                CldcTransactionRecordImpl r;
                for (int i = 0; i < recordIDs.length; i++) {
                    int recId = recordIDs[i];
                    r = readTransactionRecord(store, recId);
                    if (null != r &&
                        (r.getApplicationID() == applicationID) &&
                        r.wasMissed()) {
                        store.deleteRecord(recId);
                    }
                }
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        }

        synchronized (tsLock) {
            /* count missed and reserved transactions */
            CldcTransactionRecordImpl r;
            for (int i = (inProcessRecords.size() - 1); i >= 0; i--) {
                r = (CldcTransactionRecordImpl)
                    inProcessRecords.elementAt(i);
                if (r.wasMissed() &&
                    (r.getApplicationID() == applicationID)) {
                    inProcessRecords.removeElementAt(i);
                }
            }
        }
    
protected voidremoveMissedTransaction(int appID)
Remove missed transaction for give application.

param
appID application payment id
throws
IOException if there is an error accessing storage

        CldcTransactionRecordImpl[] recs =
            (CldcTransactionRecordImpl[]) getMissedTransactions(appID);
        if (null == recs) {
            return;
        }
        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {
                for (int i = 0; i < recs.length; i++) {
                    store.deleteRecord(recs[i].getRecordID());
                }
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " + ex.getMessage());
        }
    
public intreserve(int applicationID, com.sun.j2me.payment.Transaction transaction)
Reserves space for the given transaction in the store. It should be called before any call to the addTransaction method to ensure that the addTransaction method won't fail later (when it is inappropriate) due to full store. This method can apply some store policies, like enforcing a maximum number of missed transactions per MIDletSuite.

The applicationID identifies the application (MIDlet suite) to the transaction store. MIDlet suites which are run directly can use negative application IDs to avoid permanent storing of created transaction records.

param
applicationID the application id
param
transaction the transaction
return
an unique ID created for the transaction
throws
IOException indicates that the store is full or won't accept any further transaction records from that application


        /* assign transactionID */
        int transactionID;
        try {
            TransactionStorageImpl store = 
                    new TransactionStorageImpl(securityToken, false);
            try {

                int count = 0;
                /* count missed and reserved transactions */
                CldcTransactionRecordImpl r;
                for (int i = 0; i < inProcessRecords.size(); i++) {
                    r = (CldcTransactionRecordImpl)
                        inProcessRecords.elementAt(i);
                    if (r.wasMissed() &&
                        (r.getApplicationID() == applicationID)) {
                        count++;
                    }
                }

                /* check the limit */
                if (count >= MISSED_TRANSACTIONS_LIMIT) {
                    throw new IOException("No more space for records");
                }

                String applicationName = transaction.
                              getTransactionModule().getMIDlet().
                              getAppProperty(MIDletSuiteImpl.SUITE_NAME_PROP);

                /* assign transactionID */
                int nextTransactionID = getIntFromByteArray(
                    store.getRecord(NEXT_TRANSACTION_RECORD_ID));
                transactionID = nextTransactionID++;
                if (nextTransactionID <= 0) {
                    nextTransactionID = 1;
                }
                transaction.setTransactionID(transactionID);

                /* don't reserve space if applicationID < 0 */
                if (applicationID > 0) {
                    /* create reserved transaction */
                    CldcTransactionRecordImpl newTransactionRecord =
                        new CldcTransactionRecordImpl(
                            applicationID, applicationName,
                            transaction, System.currentTimeMillis(),
                            false);

                    store.setRecord(NEXT_TRANSACTION_RECORD_ID,
                                    getByteArrayFromInt(nextTransactionID));

                    writeTransactionRecord(newTransactionRecord, store, 0);

                    inProcessRecords.addElement(newTransactionRecord);
                } else {
                    /* create reserved fake transaction */
                    CldcTransactionRecordImpl newTransactionRecord =
                        new CldcTransactionRecordImpl(
                            applicationID, applicationName,
                            transaction, System.currentTimeMillis(),
                            true);
                    inProcessRecords.addElement(newTransactionRecord);
                }
            } finally {
                store.closeStore();
            }
        } catch (RecordStoreException ex) {
            throw new IOException("Storage Failure: " +
                                  ex.getMessage());
        }
        return transactionID;
    
public voidsetDelivered(int transactionID)
This method is called after the application is successfully notified about the transaction with the given transaction ID.

param
transactionID the transaction ID
throws
IOException indicates a storage failure

        CldcTransactionRecordImpl transactionRecord = null;

        synchronized (tsLock) {
            transactionRecord = findMissedRecord(transactionID);
            if (transactionRecord == null) {
                throw new IllegalArgumentException("Not a stored transaction");
            }

            inProcessRecords.removeElement(transactionRecord);
        }
        
        if (!transactionRecord.isFake()) {
            transactionRecord.setStatus(CldcTransactionRecordImpl.PASSED);

            try {
                TransactionStorageImpl store = 
                        new TransactionStorageImpl(securityToken, false);
                try {
                    if (transactionRecord.getRecordID() > 0) {
                        writeTransactionRecord(transactionRecord, 
                                store, transactionRecord.getRecordID());
                    }
                    // limit the passed transactions count
                    limitPassedRecords(store);
                } finally {
                    store.closeStore();
                }
            } catch (RecordStoreException ex) {
                throw new IOException("Storage Failure " + ex.getMessage());
            }
        }
    
public booleanwasDelivered(int transactionID)
It returns true if the setDelivered method was called for the given transaction ID.

param
transactionID the transaction ID
return
true if transaction with Transaction ID were delivered to user
throws
IOException indicates a storage failure

        CldcTransactionRecordImpl transactionRecord = findMissedRecord(
            transactionID);
        return transactionRecord == null;
    
private voidwriteTransactionRecord(CldcTransactionRecordImpl record, TransactionStorageImpl st, int recId)
This service function writes TransactionRecord into store.

param
record CldcTransactionRecordImpl reference
param
st TransactionStorageImpl reference
param
recId ID of the record to be replaced or 0
throws
IOException if an I/O error occurs
throws
RecordStoreException if a general record store exception occurs

        
        /* formate transaction inside ByteArray */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream os = new DataOutputStream(bos);
        record.write(os);
        byte[] data = bos.toByteArray();

        if (recId == 0) {
            recId = st.addRecord(data);
            record.setRecordID(recId);
        } else {
            st.setRecord(recId, data);
        }