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

CldcTransactionStoreImpl.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.j2me.payment;

import java.io.*;
import java.util.Hashtable;
import java.util.Random;
import java.util.Vector;
import javax.microedition.io.Connector;

import com.sun.midp.io.j2me.storage.*;

import javax.microedition.payment.TransactionRecord;

import com.sun.j2me.payment.PaymentInfo;
import com.sun.j2me.payment.ProviderInfo;
import com.sun.j2me.payment.Transaction;
import com.sun.j2me.payment.TransactionStore;

import com.sun.midp.midletsuite.*;
import com.sun.midp.security.*;
import javax.microedition.rms.*;

/**
 * This class implements the transaction store. It uses RMS
 * to store the transaction records.
 *
 */
public final class CldcTransactionStoreImpl implements TransactionStore {

    /**
     * This class has a different security domain than the MIDlet suite.
     * The token is initialized with a value handed to class constructor.
     */
    private static SecurityToken securityToken;

    /** The list of Transaction Records with reserved and missed status */
    private Vector inProcessRecords = new Vector();

    /**
     * The reference to the property containing maximum number of passed
     * transactions
     */
    private static final String TRANSACTIONS_LIMIT_PROPERTY =
        "payment.transactions.limit";
    /** The maximum number of passed transactions */
    private static final int DEFAULT_TRANSACTIONS_LIMIT = 16;

    /** The maximum number of missed transactions per application. */
    static final int MISSED_TRANSACTIONS_LIMIT = 4;
    /** The maximum total number of past transactions. */
    static final int PASSED_TRANSACTIONS_LIMIT;

    /** The RMS record ID of the NextTransactionID counter */
    private static final int NEXT_TRANSACTION_RECORD_ID = 1;
    /** The RMS record ID of the NextApplicationD counter */
    private static final int NEXT_APPLICATION_RECORD_ID = 2;

    /** The number of bytes in the int */
    private static final int SIZE_OF_INT = 4;

    /** lock used to synchronize this Transaction Store */
    Object tsLock;
    
    static {
        // 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;
    }


    /**
     * Creates a new instance of <code>CldcTransactionStoreImpl</code>. It needs
     * a security token to perform file system manipulations.
     *
     * @param securityToken the security token
     * @throws IOException indicates a storage failure
     */
    public CldcTransactionStoreImpl(SecurityToken securityToken)
        throws IOException {
        this.securityToken = securityToken;
        tsLock = new Object();
        initStore();
    }

    /**
     * Adds the given transaction to the store. It returns a new transaction
     * record for the transaction. This transaction record must have its
     * <code>wasMissed</code> flag cleared.
     *
     * @param transaction the transaction
     * @return the new transaction record
     * @throws IOException indicates a storage failure
     */
    public TransactionRecord addTransaction(
        Transaction transaction) throws IOException {
        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();
    }

    /**
     * It returns <code>true</code> if the <code>setDelivered</code> 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
     */
    public boolean wasDelivered(int transactionID)
        throws IOException {
        CldcTransactionRecordImpl transactionRecord = findMissedRecord(
            transactionID);
        return transactionRecord == null;
    }

    /**
     * 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
     */
    public void setDelivered(int transactionID)
        throws IOException {
        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());
            }
        }
    }


    /**
     * Returns an identification number, which can be used as
     * <code>applicationID</code> in the other methods. During installation
     * each payment supporting <code>MIDletSuite</code> should get such number
     * and have it stored. From that point this number will identify that
     * <code>MIDletSuite</code> to the transaction store.
     *
     * @return the payment application id
     * @throws IOException indicates a storage failure
     */
    public int getNextApplicationID() throws IOException {
        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;
    }

    /**
     * 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 <code>wasMissed</code> flag set.
     *
     * @param applicationID the application ID
     * @return the array of the missed transaction records
     * @throws IOException indicates a storage failure
     */
    public TransactionRecord[] getMissedTransactions(
        int applicationID) throws IOException {
        return getMissedTransactions(applicationID, false);
    }

    /**
     * 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
     */
    private Vector getTransactions(int applicationID, boolean wasMissed)
        throws IOException {
        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;
    }
    /**
     * 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 <code>wasMissed</code> flag cleared.
     *
     * @param applicationID the application ID
     * @return the array of the passed transaction records
     * @throws IOException indicates a storage failure
     */
    public TransactionRecord[] getPassedTransactions(
        int applicationID) throws IOException {

        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;
    }

    /**
     * Reserves space for the given transaction in the store. It should be
     * called before any call to the <code>addTransaction</code> method to
     * ensure that the <code>addTransaction</code> 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 <code>MIDletSuite</code>.
     * <p>
     * The <code>applicationID</code> 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
     */
    public int reserve(int applicationID, Transaction transaction)
        throws  IOException {

        /* 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;
    }

    /**
     * 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 <code>removeApplicationRecords</code> method).
     *
     * @param applicationID the application ID
     * @return the size used by the application
     * @throws IOException indicates a storage failure
     */
    public int getSizeUsedByApplication(int applicationID)
        throws  IOException {

        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;
    }


    /**
     * 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
     */
    public void removeApplicationRecords(int applicationID)
        throws  IOException {
        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);
                }
            }
        }
    }

    /**
     * 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
     */
    public void cleanUp() throws IOException {
        try {
            TransactionStorageImpl.deleteStore(securityToken);
        } catch (RecordStoreException ex) {
            // Nothing to do
        }

        initStore();
    }

    /**
     * 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 <code>wasMissed</code> 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
     */
    TransactionRecord[] getMissedTransactions(
        int applicationID, boolean fakeOnly) throws IOException {
        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;
    }

// === DEBUG MODE ===
    /**
     * 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
     */
    void generateFakeRecords(int applicationID,
                     String applicationName, PaymentInfo paymentInfo,
                     String featurePrefix, int[] validProviders, int count)
        throws IOException {
        // 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;
        }
    }

// === DEBUG MODE ===

    /**
     * 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
     */
    private CldcTransactionRecordImpl findRecord(int transactionID, int status)
         throws IOException {

        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;
    }

    /**
     * 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
     */
    private CldcTransactionRecordImpl findMissedRecord(int transactionID)
        throws IOException {
        return findRecord(transactionID, CldcTransactionRecordImpl.MISSED);
    }

    /**
     * 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
     */
    private CldcTransactionRecordImpl findReservedRecord(int transactionID)
        throws IOException {
        return findRecord(transactionID, CldcTransactionRecordImpl.RESERVED);
    }

    /**
     * retrives Int from the ByteArray
     *
     * @param data the array of bytes
     * @return integer value converted from byte array or -1
     */
    static int getIntFromByteArray(byte[] data) {
        if (data.length == SIZE_OF_INT) {
            return (((int) (data[0]) << 24) |
                    ((int) (data[1]) << 16) |
                    ((int) (data[2]) << 8) |
                    (int) (data[3]));
        }
        return -1;
    }

    /**
     * puts integer into the ByteArray
     *
     * @param v the integer value
     * @return byte array containing the integer value
     */
    static byte[] getByteArrayFromInt(int v) {
        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;
    }

    /**
     * Initializes the store. It's used when the transaction store file doesn't
     * exists.
     *
     * @throws IOException indicates a storage failure
     */
    private void initStore() throws IOException {
        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 */
        }
    }

    /**
     * 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
     */
    private void limitPassedRecords(TransactionStorageImpl st) 
            throws RecordStoreException, IOException {
        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);
        }
    }


    /**
     * Remove missed transaction for give application.
     *
     * @param appID application payment id
     * @throws IOException if there is an error accessing storage
     */
    protected void removeMissedTransaction(int appID) throws IOException {
        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());
        }
    }

    /**
     * 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
     */
    private CldcTransactionRecordImpl readTransactionRecord(
        TransactionStorageImpl st, int recId) throws IOException,
        RecordStoreException {
        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;
    }

    /**
     * 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
     */
    private void writeTransactionRecord(CldcTransactionRecordImpl record,
        TransactionStorageImpl st, int recId) throws IOException,
        RecordStoreException {
        
        /* 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);
        }
    }
}