| 
 | MIDP3.0 | |||||||||
| PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES | |||||||||
See:
          Description
| Interface Summary | |
|---|---|
| RecordComparator | An interface defining a comparator which compares two records (in an implementation-defined manner) to see if they match or what their relative sort order is. | 
| RecordEnumeration | An interface representing a bidirectional record store Record enumerator. | 
| RecordFilter | An interface defining a filter which examines a record to see if it matches (based on an application-defined criteria). | 
| RecordListener | A listener interface for receiving Record Changed/Added/Deleted events from a record store. | 
| Class Summary | |
|---|---|
| RecordStore | A class representing a record store. | 
| RecordStoreInfo | A class representing information about a RecordStore, including authorization mode, encryption status, writeable status, and size information. | 
| Exception Summary | |
|---|---|
| InvalidRecordIDException | Thrown to indicate an operation could not be completed because the record ID was invalid. | 
| RecordStoreException | Thrown to indicate a general exception occurred in a record store operation. | 
| RecordStoreFullException | Thrown to indicate an operation could not be completed because the record store system storage is full. | 
| RecordStoreNotFoundException | Thrown to indicate an operation could not be completed because the record store could not be found. | 
| RecordStoreNotOpenException | Thrown to indicate that an operation was attempted on a closed record store. | 
| SecureRecordStoreException | Thrown to indicate that a problem occurred during the process of Encrypting or Decrypting data of a Secure RecordStore. | 
The Mobile Information Device Profile provides a mechanism for MIDlets to persistently store data and later retrieve it.
Unless otherwise noted, passing a null argument to a constructor or method in any class or interface in this package MUST cause a NullPointerException to be thrown.
The MIDP provides a mechanism for MIDlets to persistently store data and retrieve it later. This persistent storage mechanism, called the Record Management System (RMS), is modeled after a simple record-oriented database.
A record store consists of a collection of records that will remain persistent across multiple invocations of a MIDlet. The implementation is responsible for making its best effort to maintain the integrity of the MIDlet's record stores throughout the normal use of an implementation, including device reboots, power loss, etc. The actual process of persisting record store data is the responsibility of the implementation, and MAY occur asynchronously, even as part of a cleanup process when the device restarts.
        Record stores are created in platform-dependent locations, which
        are not exposed to MIDlets. The naming space for record stores
        is controlled at the MIDlet suite granularity. MIDlets within a
        MIDlet suite are allowed to create multiple record stores, as
        long as they are each given different names. LIBlet-owned record
        stores (i.e. those provisioned via LIBlet-Persistent-Data-URL-<n>) 
        will have a namespace based on the owning LIBlet. Note that multiple
        versions of the same LIBlet may exist on a device, and each of these
        LIBlet versions will have its own record store namespace. When a MIDlet suite
        is deleted from a platform, all record stores associated with
        its MIDlets MUST be deleted. When a LIBlet is deleted, all of its 
        associated record stores MUST also be deleted. MIDlets within a MIDlet suite
        can access one another's record stores directly. The RecordStore APIs
        allow for the explicit sharing of record stores if the
        MIDlet creating the RecordStore chooses to give such
        permission.
    
        A MIDlet Suites's record stores are uniquely named using the unique name of the
        MIDlet suite plus the name of the record store. MIDlet suites
        are identified by the
        MIDlet-Vendor
        and
        MIDlet-Name
        attributes from the application descriptor.
    
Record store names are case sensitive and MAY consist of any combination of between one and 32 Unicode characters inclusive. Record store names MUST be unique within the scope of a given MIDlet suite. In other words, MIDlets within a MIDlet suite are not allowed to create more than one record store with the same name; however, a MIDlet in one MIDlet suite is allowed to have a record store with the same name as a MIDlet in another MIDlet suite. In that case, the record stores are still distinct and separate.
        Record stores MAY be created by the AMS during MIDlet suite or LIBlet installation.
        Record stores associated with the MIDlet Suite or LIBlet are created if the
        
        MIDlet-Persistent-Data-URL-<n>
        or
        
        LIBlet-Persistent-Data-URL-<n>
        attribute is present in their respective manifests.
        See RMS Data Provisioning
        for more details.
    
        For MIDlet Suites, the record store created may be private or shared based
        on the value of the authmode field within the RMS data file pointed to by the
        
        MIDlet-Persistent-Data-URL-<n> attribute.
        The value of the authmode field may be one of :
        
AUTHMODE_ANY
                to allow access to any MIDlet suite,
            AUTHMODE_APPLEVEL
                to allow access only to certain identified MIDlet suites, or
            AUTHMODE_PRIVATE
                to allow access only to the current MIDlet suite
            
        For LIBlets, using the
        
        LIBlet-Persistent-Data-URL-<n>
        attribute in the LIBlet manifest is the only way to create a
        record store that is owned by the LIBlet. A record store owned
        by a LIBlet MUST have its authmode set to
        AUTHMODE_PRIVATEAUTHMODE_PRIVATERecordStore owned by a LIBlet it declares
        a dependency on by passing in the RecordStore name, LIBlet vendor, and LIBlet name to the
                RecordStore.openRecordStore (plaintext) or
                RecordStore.openRecordStore (encrypted) method call.
        A MIDlet will not be able to access a LIBlet's record stores if its 
        MIDlet Suite did not declare a dependency on that LIBlet.
        
It is possible for a provisioned RMS data file to contain no records; that is, the record store may be an empty record store and may be populated by a MIDlet at runtime. Provisioned record stores are available for use immediately upon creation.
Record store sharing is accomplished through the ability to name a RecordStore in another MIDlet suite, and by defining the accessibility rules related to the authentication of the two MIDlet suites.
        A MIDlet MAY control access to a shared record store using
        AUTHMODE_APPLEVEL
        and the application level access control mechanism as described in 
        Application
        Level Access Authorization. If the MIDlet JAD/Manifest does not contain
        any of the access authorization attributes, the
        AUTHMODE_APPLEVEL has no effect,
        and the authmode defaults to AUTHMODE_ANY.
    
Access controls are defined when record stores to be shared are created. Access controls are enforced when record stores are opened. A MIDlet suite defines access control by using access modes. The access modes allow private use or shareable with any other MIDlet suite. Shareable record stores of two kinds can be created :
        Implementations MUST allow shared record stores to be opened concurrently
        by multiple applications. Successful updates to records MUST be visible to
        all applications when the update is complete. All RecordListeners
        to shared record stores must be notified after a record changes, regardless
        of the MIDlet that registered the listener and regardless of which MIDlet made
        the record update, both within and across MIDlets.
    
        An application may optionally request that a RecordStore's record data be encrypted
        on the device. If requested, the implementation MUST encrypt the records before
        they are persisted and automatically decrypt them when they are fetched.
        Implementations MUST encrypt secure record store data using either a hardware
        or software based cryptographically strong algorithm; an example is a symmetric-key cipher
        such as AES, DES, or Blowfish. The encryption key MUST be derived from the password supplied.
        Encrypted record stores are only as secure as the handling of the key;
        if a MIDlet stores the password within its code, security is not a
        reasonable expectation. For improved security, the MIDlet should ask the
        user for the password on each invocation of the MIDlet. The password is a
        String that consists of Unicode characters with a recommended
        minimum length of eight characters.
    
Note: In RMS Interchange file format, the encryption algorithm and standards for key derivation are specified to ensure interoperability between devices, whereas for on-device encryption of record stores, the implementation may choose to use the same encryption standards as for the RMS interchange format or follow the guidelines mentioned above.
        No locking operations are provided in this API. Record store
        implementations MUST ensure that all individual record store
        operations are atomic, synchronous, and serialized so that no
        corruption occurs with multiple accesses, from within or across
        execution environments. However, if a MIDlet
        uses multiple threads to access a record store, it is the
        MIDlet's responsibility to coordinate this access, or unintended
        consequences may result. For example, if two threads in a MIDlet
        both call RecordStore.setRecord() concurrently on
        the same record, the record store will serialize these calls
        properly, and no RecordStore corruption will occur as a result.
        However, one of the writes will be subsequently overwritten by
        the other, which may cause problems within the
        MIDlet. Similarly, if an implementation performs transparent
        synchronization of a record store or other access, it
        is the implementation's responsibility to enforce exclusive access to
        the record store between the MIDlets and synchronization
        engine. The implementation MUST NOT serialize calls to
        RecordListeners across execution environments.  The implementation MUST call 
        the RecordListener callbacks in the order in which additions, deletions, or changes 
        took place on a record. Implementations MAY coalesce record listener callbacks 
        that resulted from multiple changes to a particular record. Implementations MUST NOT 
        discard any record listener callbacks that resulted from record additions, deletions, or changes. 
    
        This record store API uses long integers for time/date stamps,
        in the format used by System.currentTimeMillis().
        The record store is time stamped with the last time it was
        modified. The record store also maintains a version, which is an
        integer that is incremented for each operation that modifies the
        contents of the record store. These are useful for
        synchronization engines as well as applications.
    
        The static method RecordStore.openRecordStore is overloaded 
        to enable applications to open and create different kinds of  
        RecordStores. Some code examples are given below. 
                
RecordStore rstore = RecordStore.openRecordStore ( “MyRecordStore”, true); // create one if not found 
                RecordStore rstore = RecordStore.openRecordStore (“MyRecordStore”, false); // open only an existing RecordStore. 
        int authmode = AUTHMODE_ANY; // or AUTHMODE_APPLEVEL 
                boolean writable = true; // or false for read-only 
                RecordStore rstore = RecordStore.openRecordStore (“MyRecordStore”, true, AUTHMODE_ANY, writable); 
        String password = getPasswordFromUser (); 
                boolean writable = true;  // or false for read-only 
                RecordStore rstore = RecordStore.openRecordStore (“MyRecordStore”, true, AUTHMODE_PRIVATE, writable, password); 
        String password = getPasswordFromUser (); 
                boolean writable = true; // or false for read-only  
                RecordStore rstore = RecordStore.openRecordStore (“MyRecordStore”, true, AUTHMODE_APPLEVEL, writable, password);
        RecordStore rstore = RecordStore.openRecordStore ( “OtherRecordStore”, “OtherVendor”, “OtherSuite”); 
         String password = getPasswordFromUser ();
                RecordStore rstore = RecordStore.openRecordStore ( “OtherRecordStore”, “OtherVendor”, “OtherSuite”, password);In MIDP 2.0 there was no efficient way to limit the enumeration on a subset of records in a record store. The RecordComparator and RecordFilter are applied on all the records of the record store. For a larger record store, finding a particular record results in call backs on the RecordComparator and RecordFilter for all the records in the store, which is a lot of overhead. The record tags provide an option to the developer to reduce this overhead significantly.
        Record tags allow MIDlet developers to associate an integer tag
        with each record. These tags are specified while calling addRecord or
        setRecord to the record store. The developer can now specify these tags
        when calling enumeration, and the implementation MUST only return those
        records for which the tags match.
    
As an example, if a record store has 100 records and the developer tags 10 records with the TAG value of 10. The developer can now call enumeration with tag value 10 and the implementation will only return those records with the tag value of 10. The developer has significantly reduced the number of records that need to be matched or compared.
        When records are added with the legacy addRecord and
        setRecord API's, the default value of tag MUST be 0.
    
Tags are not required to be encrypted by the implementation when a record store is locally encrypted. Since record tags may not be encrypted before being written to persistent storage, MIDlet developers should avoid storing sensitive information in clear text in record tags.
        Records are arrays of bytes. Developers can use
        DataInputStream and DataOutputStream
        as well as ByteArrayInputStream and
        ByteArrayOutputStream to pack and unpack different
        data types into and out of the byte arrays.
    
        Records are uniquely identified within a given record store by
        their recordId , which is an integer value. This
        recordId is used as the primary key for the
        records. The first record created in a record store will have
        recordId equal to 1, and each subsequent
        recordId will monotonically increase by one. For
        example, if two records are added to a record store, and the
        first has a recordId of n, the next will have a
        recordId of n+1. MIDlets can create other indices
        by using the RecordEnumeration class.
    
Versions of the MIDP specification previous to MIDP 3.0 did not provide either for the provisioning or interchange of RMS record stores. This lack of a common RMS format resulted in limited application portability.
MIDP 3.0 introduces support for a standalone secure binary file format that can be provisioned to a device by URL reference from MIDlet suite's JAD or manifest file.
        The RecordStore class supports serialization/deserialization of
        RMS data into this file format with optional encryption.
    
The RMS file format is used for persistent data provisioning as well as RMS interchange. Each RMS file MUST contain exactly one serialized record store.
        RMS data MAY be provisioned along with the application via
        standalone files and/or files embedded in the application JAR.
        RMS files are listed in the
        
        MIDlet-Persistent-Data-URL-<n>
        attribute. See 
        RMS Data Provisioning for details.
    
The recommended RMS file extension is ".rms" and a MIME type is "application/vnd.jcp.javame.midlet-rms".
The RMS data is stored on the device in implementation specific format. In order for RMS data to be exchanged between implementations, it MUST be serialized into this implementation independent format.
The following crypto algorithm, mode, padding scheme, message digest algorithms, and password based key derivation function MUST be supported.
Cipher used for encryption is represented by a transformation string in the form of “algorithm” or “algorithm/mode/padding” (e.g. “AES/CBC/PKCS5Padding” is required to be supported). Note: this format is the same as defined by the SATSA specification for the cipher transformation string. The standard names for algorithm, mode, padding scheme and message digest algorithm are defined in JCE specification.
The following tables define the format of a serialized RMS data file used for interchange of the RMS data. Both encrypted and unencrypted formats are supported. The file header contains information on whether the file is encrypted or not. For an encrypted file, the name of the Cipher Algorithm is placed in the Encryption Parameters portion of the file and contains the name of the Cipher Algorithm used. The AMS MAY use this information to discover the required decryption algorithm.
| Header | ||
|---|---|---|
| FileIdentifier | 6 bytes (0x4d, 0x49, 0x44, 0x52, 0x4d, 0x53 or “MIDRMS”) | A unique identifier of the file format. | 
| VersionNumber | 2 bytes First byte: major version Second byte: minor version | RMS file format version
             | 
| Encrypted | 1 byte (boolean) 0: plain-text (not encrypted) file 1: encrypted file. | Encryption flag
                 | 
| MessageDigestAlgorithm | 
                UTF String as specified by DataOutputStream.writeUTF
                 | Message digest algorithm name. e.g. "SHA-1" | 
| RecordStoreData | ||
|---|---|---|
| RecordStore | Record Store Attributes | |
| Record 1 | First record | |
| ... | ||
| Record N | Last record | |
| Record | ||
|---|---|---|
| RecordID | 4 bytes, high byte first as specified by DataOutputStream.writeInt() | ID of this Record | 
| Tag | 4 bytes, high byte first as specified by DataOutputStream.writeInt() | Tag of this record 0: if this record has no tag | 
| RecordDataSize | 4 bytes, high byte first as specified by DataOutputStream.writeInt() | Record size | 
| RecordData | Variable size of RecordDataSize bytes | Record data byte array RecordData[0], ... RecordData[<RecordDataSize>-1] | 
| MessageDigest | ||
|---|---|---|
| MessageDigestLength | 4 bytes, high byte first as specified by DataOutputStream.writeInt() | Message digest length in bytes | 
| MessageDigest | Variable size of MessageDigestLength bytes | Message digest of the stream containing Encryption parameters and non-encrypted RecordStoreData as byte array: MessageDigest[0], ... MessageDigest[<MessageDigestLength>-1] | 
Note : For Encrypted RMS Interchange Format files, the entire portion of the stream from RecordStore Data to the end of the Message Digest is encrypted.
The following example uses the Record Management System to store and retrieve high scores for a game. In the example, high scores are stored in separate records, and sorted when necessary using a RecordEnumeration.
   
import javax.microedition.rms.*;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
/**
 * A class used for storing and showing game scores.
 */
public class RMSGameScores
implements RecordFilter, RecordComparator
{
    /*
     * The RecordStore used for storing the game scores.
     */
    private RecordStore recordStore = null;
    /*
     * The player name to use when filtering.
     */
    public static String playerNameFilter = null;
    /*
     * Part of the RecordFilter interface.
     */
    public boolean matches(byte[] candidate)
    throws IllegalArgumentException
    {
        // If no filter set, nothing can match it.
        if (this.playerNameFilter == null) {
            return false;
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(candidate);
        DataInputStream inputStream = new DataInputStream(bais);
        String name = null;
        try {
            int score = inputStream.readInt();
            name = inputStream.readUTF();
        }
        catch (EOFException eofe) {
            System.out.println(eofe);
            eofe.printStackTrace();
        }
        catch (IOException eofe) {
            System.out.println(eofe);
            eofe.printStackTrace();
        }
        return (this.playerNameFilter.equals(name));
    }
    /*
     * Part of the RecordComparator interface.
     */
    public int compare(byte[] rec1, byte[] rec2)
    {
        // Construct DataInputStreams for extracting the scores from
        // the records.
        ByteArrayInputStream bais1 = new ByteArrayInputStream(rec1);
        DataInputStream inputStream1 = new DataInputStream(bais1);
        ByteArrayInputStream bais2 = new ByteArrayInputStream(rec2);
        DataInputStream inputStream2 = new DataInputStream(bais2);
        int score1 = 0;
        int score2 = 0;
        try {
            // Extract the scores.
            score1 = inputStream1.readInt();
            score2 = inputStream2.readInt();
        }
        catch (EOFException eofe) {
            System.out.println(eofe);
            eofe.printStackTrace();
        }
        catch (IOException eofe) {
            System.out.println(eofe);
            eofe.printStackTrace();
        }
        // Sort by score
        if (score1 < score2) {
            return RecordComparator.PRECEDES;
        }
        else if (score1 > score2) {
            return RecordComparator.FOLLOWS;
        }
        else {
            return RecordComparator.EQUIVALENT;
        }
    }
    /**
     * The constructor opens the underlying record store,
     * creating it if necessary.
     */
    public RMSGameScores()
    {
        //
        // Create a new record store for this example
        //
        try {
            recordStore = RecordStore.openRecordStore("scores", true);
        }
        catch (RecordStoreException rse) {
            System.out.println(rse);
            rse.printStackTrace();
        }
    }
    /**
     * Add a new score to the storage.
     *
     * @param score the score to store.
     * @param playerName the name of the play achieving this score.
     */
    public void addScore(int score, String playerName)
    {
        //
        // Each score is stored in a separate record, formatted with
        // the score, followed by the player name.
        //
        int recId;  // returned by addRecord but not used
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream outputStream = new DataOutputStream(baos);
        try {
            // Push the score into a byte array.
            outputStream.writeInt(score);
            // Then push the player name.
            outputStream.writeUTF(playerName);
        }
        catch (IOException ioe) {
            System.out.println(ioe);
            ioe.printStackTrace();
        }
        // Extract the byte array
        byte[] b = baos.toByteArray();
        // Add it to the record store
        try {
            recId = recordStore.addRecord(b, 0, b.length);
        }
        catch (RecordStoreException rse) {
            System.out.println(rse);
            rse.printStackTrace();
        }
    }
    /**
     * A helper method for the printScores methods.
     */
    private void printScoresHelper(RecordEnumeration re)
    {
        try {
            while(re.hasNextElement()) {
                int id = re.nextRecordId();
                ByteArrayInputStream bais = new ByteArrayInputStream(recordStore.getRecord(id));
                DataInputStream inputStream = new DataInputStream(bais);
                try {
                    int score = inputStream.readInt();
                    String playerName = inputStream.readUTF();
                    System.out.println(playerName + " = " + score);
                }
                catch (EOFException eofe) {
                    System.out.println(eofe);
                    eofe.printStackTrace();
                }
            }
        }
        catch (RecordStoreException rse) {
            System.out.println(rse);
            rse.printStackTrace();
        }
        catch (IOException ioe) {
            System.out.println(ioe);
            ioe.printStackTrace();
        }
    }
    /**
     * This method prints all of the scores sorted by game score.
     */
    public void printScores()
    {
        try {
            // Enumerate the records using the comparator implemented
            // above to sort by game score.
            RecordEnumeration re = recordStore.enumerateRecords(null, this,
                            true);
            printScoresHelper(re);
        }
        catch (RecordStoreException rse) {
            System.out.println(rse);
            rse.printStackTrace();
        }
    }
    /**
     * This method prints all of the scores for a given player,
     * sorted by game score.
     */
    public void printScores(String playerName)
    {
        try {
            // Enumerate the records using the comparator and filter
            // implemented above to sort by game score.
            RecordEnumeration re = recordStore.enumerateRecords(this, this,
                            true);
            printScoresHelper(re);
        }
        catch (RecordStoreException rse) {
            System.out.println(rse);
            rse.printStackTrace();
        }
    }
    public static void main(String[] args)
    {
        RMSGameScores rmsgs = new RMSGameScores();
        rmsgs.addScore(100, "Alice");
        rmsgs.addScore(120, "Bill");
        rmsgs.addScore(80, "Candice");
        rmsgs.addScore(40, "Dean");
        rmsgs.addScore(200, "Ethel");
        rmsgs.addScore(110, "Farnsworth");
        rmsgs.addScore(220, "Farnsworth");
        System.out.println("All scores");
        rmsgs.printScores();
        System.out.println("Farnsworth's scores");
        RMSGameScores.playerNameFilter = "Farnsworth";
        rmsgs.printScores("Farnsworth");
    }
}
| 
 | MIDP3.0 | |||||||||
| PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES | |||||||||