MEEP v.8 Final Release, April 15, 2014

Package javax.microedition.rms

[OPTIONAL] Mechanism for applications to persistently store data and later retrieve it.

See: Description

Package javax.microedition.rms Description

[OPTIONAL] Mechanism for applications to persistently store data and later retrieve it.

RMS is modeled after a simple record-oriented database.

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.

Record Store

A record store consists of a collection of records that will remain persistent across multiple invocations of an application. The implementation is responsible for making its best effort to maintain the integrity of the application'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 applications. The naming space for record stores is controlled at the application granularity. Applications are allowed to create multiple record stores, as long as they are each given different names. When an application is deleted from a platform, all record stores associated with it MUST be deleted. The RecordStore API allows for the explicit sharing of record stores if the application creating the RecordStore chooses to give such permission.

Naming Record Stores

An application's record stores are uniquely named using the unique name of the application plus the name of the record store. Applications 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 application. In other words, applications are not allowed to create more than one record store with the same name; however, an application is allowed to have a record store with the same name as another application. In that case, the record stores are still distinct and separate.

Shared Record Stores

Record store sharing is accomplished through the ability to name a record store of another application, and by defining the accessibility rules related to the authentication of the two application suites.

Access controls are defined when record stores to be shared are created. Access controls are enforced when record stores are opened. An application suite defines access control by using access modes. The access modes allow private use or shareable with any other application suite.

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 application that registered the listener and regardless of which application made the record update, both within and across applications.

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 an application stores the password within its code, security is not a reasonable expectation.

Atomicity of RecordStore Access

No locking operations are provided in this API. Record store implementations 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 an application uses multiple threads to access a record store, it is the application's responsibility to coordinate this access, or unintended consequences may result. For example, if two threads in an application both call RecordStore.setRecord() concurrently on the same record, the record store will serialize these calls properly, and no database corruption will occur as a result. However, one of the writes will be subsequently overwritten by the other, which may cause problems within the application. Similarly, if a platform performs transparent synchronization of a record store or other access is the implementation's responsibility to enforce exclusive access to the record store between the applications 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 previous specification versions, 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 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, application 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. Applications can create other indices by using the RecordEnumeration. class.

RMS Example

The following example uses the Record Management System to store and retrieve stock quotes. In the example, values are stored in separate records, and sorted when necessary using a RecordEnumeration.

 import javax.microedition.rms.*;
 import javax.microedition.midlet.*;
 import javax.microedition.io.*;
 import java.io.*;
 import java.util.Vector;

 // An application for handling stock quotes.
 public class StockQuotes extends MIDlet
                          implements RecordFilter, RecordComparator {
      // ...

      // The RecordStore used for storing the result values.
      private RecordStore recordStore = null;

      //The stock name to use when filtering.
      private String stockNameFilter = null;#

      // Part of the RecordFilter interface.
      public boolean matches(byte[] candidate) throws IllegalArgumentException {
          // If no filter set, nothing can match it.
          if (this.stockNameFilter == null) {
              return false;
          }

          ByteArrayInputStream bais = new ByteArrayInputStream(candidate);
          DataInputStream inputStream = new DataInputStream(bais);
          String name = null;

          try {
              int value = inputStream.readInt();
              name = inputStream.readUTF();
          } catch (EOFException eofe) {
              // ...
          } catch (IOException eofe) {
              // ...
          }
          return (this.stockNameFilter.equals(name));
      }

      // Part of the RecordComparator interface.
      public int compare(byte[] rec1, byte[] rec2) {
          // Construct DataInputStreams for extracting the stock quotes from
          // the records.
          ByteArrayInputStream bais1 = new ByteArrayInputStream(rec1);
          DataInputStream inputStream1 = new DataInputStream(bais1);
          ByteArrayInputStream bais2 = new ByteArrayInputStream(rec2);
          DataInputStream inputStream2 = new DataInputStream(bais2);
          int quote1 = 0;
          int quote2 = 0;

          try {
              // Extract the stock quotes.
              quote1 = inputStream1.readInt();
              quote2 = inputStream2.readInt();
          } catch (EOFException eofe) {
              // ...
          } catch (IOException eofe) {
              // ...
          }

          // Sort by result value
          if (quote1 < quote2) {
              return RecordComparator.PRECEDES;
          } else if (quote1 > quote2) {
              return RecordComparator.FOLLOWS;
          } else {
              return RecordComparator.EQUIVALENT;
          }
      }

      public StockQuotes() { // constructor
      }

      // Start the Application.
      public void startApp() {
          // ...

          // Create the record store
          //
          try {
              recordStore = RecordStore.openRecordStore("mystocks", true);
          } catch (RecordStoreException rse) {
              // ...
          }

          // ...
      }

      public void destroyApp(boolean unconditional) {
          try {
              if(recordStore.getNumRecords() == 0) {
                  String fileName = recordStore.getName();
                  recordStore.closeRecordStore();
                  recordStore.deleteRecordStore(fileName);
              } else {
                  recordStore.closeRecordStore();
              }
          } catch(RecordStoreNotOpenException e) {
              // ...
          } catch(RecordStoreException e) {
              // ...
          }
          notifyDestroyed();
      }

      // Add a new stock to the record store.
      public void addStock() {
          // add to database
          // String input = ...
          String quoteString = getQuote(input);
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          DataOutputStream outputStream = new DataOutputStream(baos);

          try {
              outputStream.writeUTF(quoteString);
          } catch (IOException ioe) {
              // ...
          }

          byte[] b = baos.toByteArray();
          try {
              recordStore.addRecord(b, 0, b.length);
          } catch (RecordStoreException rse) {
              // ...
          }
          // ...
      }

      // Get the quotes to be stored.
      // @param input stock name from input
      public String getQuote(String input) {
          // ...
          // (we won't tell you where we get the quotes from :-)
      }


      // List the stocks in the record store.
      public void listStocks() {
          stockList = new List( ...
          // ...
          try {
              // we don't use the filter here
              RecordEnumeration re =
                  recordStore.enumerateRecords(null, this, false);
              while(re.hasNextElement()) {
                  String stockValue = new String(re.nextRecord());
                  stockList.append( ... );
                              // stock name and value parsed from the record
              }
          } catch(RecordStoreNotOpenException e) {
              // ...
          }
          // ... (output somewhere)
      }

      // List the stocks in the record store for a particular stock name.
      public void listStocksFor(String stockName) {
          stockListForname = new List( ... );
          // ...
          try {
              // this time we use the filter
              stockNameFilter = stockName;
              RecordEnumeration re =
                  recordStore.enumerateRecords(this, this, false);

          // ... (rest of the method like listStocks()
      }
 }
 
Since:
IMP 1.0
MEEP v.8 Final Release, April 15, 2014

Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. Use of this specification is subject to license terms.