|
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
.
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 AUTHMODE_PRIVATE
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.
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 RecordListener
s
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 |