MIDP3.0

Package javax.microedition.rms

The Mobile Information Device Profile provides a mechanism for MIDlets to persistently store data and later retrieve it.

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.
 

Package javax.microedition.rms Description

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.

Persistent Storage

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.

Record Store

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.

Naming Record Stores

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.

Provisioned Record Stores

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 :

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_PRIVATE. If the authmode field within the RMS data file for the LIBlet is set to any value other than AUTHMODE_PRIVATE then the installation of the LIBlet MUST fail. Record stores created dynamically at runtime by LIBlet code are owned by the MIDlet suite of the execution environment in which the MIDlet is running. A MIDlet MAY open a RecordStore 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.

Shared Record Stores

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.

Secure Record Stores

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.

Atomicity of RecordStore Access

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.

Examples

The static method RecordStore.openRecordStore is overloaded to enable applications to open and create different kinds of RecordStores. Some code examples are given below.

Record Tags

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

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.

Persistent Data Interchange

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.

RMS Interchange File Format

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.

Table 18-1 : RMS Interchange File Format

Header

RMS file header

EncryptionParameters

Encryption Parameters (optional). Only for encrypted files.

RecordStoreData

Record store data stream. May be encrypted or unencrypted.

MessageDigestData

MessageDigest for the stream consisting of EncryptionParameters and RecordStoreData

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
For this revision of MIDP it must be 0x03, 0x00

Encrypted

1 byte (boolean)

0: plain-text (not encrypted) file

1: encrypted file.

Encryption flag
For encrypted files the EncryptionParameters section follows the header

MessageDigestAlgorithm

UTF String as specified by DataOutputStream.writeUTF
MessageDigestAlgorithmLength: 2 bytes
MessageDigestAlgorithmString: variable size of MessageDigestAlgorithmLength bytes

Message digest algorithm name.

e.g. "SHA-1"

EncryptionParameters

EncryptionAlgorithm

UTF String as specified by DataOutputStream.writeUTF
EncryptionAlgorithmLength: 2 bytes
EncryptionAlgorithmString: variable size of NameLength bytes

Cipher algorithm used in this file.

Supported values are

  1. “algorithm”

  2. “algorithm/mode/padding”

e.g. “AES/CBC/PKCS5Padding”

IVLength

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Cipher initialization vector length (if required by the cipher) in bytes.

0: if initialization vector is not required by this algorithm

IV

Variable size of IVLength bytes

Cipher initialization vector as byte array.

IV[0], ... IV[<IVLength>-1]

SaltLength

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Password salt length in bytes

Salt

Variable size of SaltLength bytes

Password salt as byte array.

Salt[0], ... Salt[<SaltLength>-1]

IterationCount

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Password iteration count.

e.g. 1000

KeyLength

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Cipher key length.

e.g. 128 bits

RecordStoreData

RecordStore

Record Store Attributes

Record 1

First record

...

Record N

Last record

RecordStore

Name

UTF String as specified by DataOutputStream.writeUTF

NameLength: 2 bytes

Name String: variable size of NameLength bytes

Record store name

LastModified

8 bytes, high byte first as specified by DataOutputStream.writeLong()

Time of last modification

As specified by System.currentTimeMillis()

Version

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Record store version

AuthMode

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Authorization mode

As specified by RecordStore.setMode(int, int)

Writeable

1 byte (boolean)

0: not writeable by other MIDlet suites (false)

1: writeable by other MIDlet suites (true)

Write access

NumberOfRecords

4 bytes, high byte first as specified by DataOutputStream.writeInt()

Number of records in this record store

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.

RMS Example

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

Since:
MIDP 1.0

MIDP3.0

Send a comment or suggestionVersion 3.0 of Mobile Information Device Profile Specification
Java is a trademark or registered trademark of Sun Microsystems, Inc. in the US and other countries. Copyright 2002-2009 Motorola Inc. Portions copyright 1993-2002 Sun Microsystems, Inc. and Motorola, Inc. All Rights Reserved.