DB Bundle

The DB Bundle provides persistent storage for various data types.

Contents:


Bundle Information

Bundle JAR

The DB Bundle is represented by the db.jar JAR for the Standard Version of the framework and the dbmio.jar JAR for the Connector Version of the framework, both placed in the bundles directory.

Import

The DB Bundle imports the following packages:

Package Exporter Description
com.prosyst.mbs.framework.event System Bundle/
ProSyst Util Full Bundle
Contains utilities for event dispatching on part of the framework.
com.prosyst.util.hash Contains the hash utilities.
com.prosyst.util.io Contains the I/O utilities for enhanced object serialization and remote access.
com.prosyst.util.ref ProSyst Util Bundle /
ProSyst Util Full Bundle
Holds the log utility, that stores log messages about the runtime status of a bundle.
com.prosyst.util.threadpool Contains the Thread Pool Manager, which provides reusable threads kept in a pool.
com.prosyst.util.timer Holds the Timer service API for launching time-based notification.
Packages imported only in dbmio.jar
org.mbs.services.io System Bundle Contains the ProSyst Connection API for receiving connection events.
org.osgi.service.io Contains the API of the OSGi IO Connector Service for accessing file storage in a URL-like manner.

Export

The DB Bundle exports the com.prosyst.mbs.services.db package, which contains means for persistent storage of data in databases.


Principles of Work

The DB Bundle distributes stored data in databases. A database is an com.prosyst.mbs.services.db.DB instance, which keeps data in nodes in the form of name/value pairs.

Database Types

Basically, there are two types of databases:

Supported Data Types

With the DB Bundle bundles can store data of the following types:

DB Structure

The data stored in a database is organized in a tree. Every node (including the root) can possess unlimited number of subnodes.

Each node can contain data of the types listed in "Supported Data Types", or can be empty, which indicates that the node has no value and is used only to form the tree hierarchy.

Syntax of Node Names

A node’s full name is a String[] formed recursively from the simple names of all its predecessors and its own simple name. All methods of the DB interface take a full name argument while the list method returns simple names. The String[] used to specify a node’s full name can be null-terminated – it can have arbitrary length and the first null value in the array indicates the end of the name.

Tip: To access the root node of a database, use String[] {} as node name.

Writing Data to a DB

The basic method for writing into a database (DB object) is setValue. You can also use the move method to move a subnode to another parent and the delete method to erase some data. Additionally, you can reserve a database by using the lock and unlock methods.

Note that in the main DB only the bundle which initially created a specific node and populated it with data can modify this data.

For illustration of how to write in a DB, refer to Listing 1.1 from the DB example in this document.

Browsing the Content of a DB

The DB Bundle provides two ways for reading the content of a DB to bundles:

By Referring Directly to the DB

The methods of the DB interface define support for the following operations:

Node by Node

The DB Bundle provides representation of DB nodes as DBNode instances. A DBNode can be used to:

Tip: It is possible that a DBNode becomes invalid between two successive read operations. To avoid failures, first check the validity of the node by using the isValid method of DBNode. Then, you can lock the parent DB by calling the lock method in order to avoid the situation where the got node might be invalidated by another entity.


Services

DB Service

The DB service, published under the com.prosyst.mbs.services.db.DB interface name, provides bundles with access to the main DB (see "Database Types").

Every bundle that writes into the main DB service is associated with a separate node within the DB root. Such a bundle-specific node is named after the bundle ID. The data in the subnodes is accessible for write operations only by the bundle that created it.

Note: As the DB service stores the data of the main DB in its private area from the framework storage, on updating the DB Bundle this data will be lost as the framework automatically clears the bundle storage area, unless the bundle is updated through the Framework Access service (for example, by using update console command from the fw group with the -k option).

Compromising between Speed and Persistence

As the DB Bundle is widely used for storing data persistently, its performance can have a great influence on the overall operation of the framework. A common case in most embedded devices is a hardware configuration where a flash card with fast read access but unpredictably slow write access is used for persistent storage. The DB Bundle executes a lot of file write operations and in this case writing data directly on the flash file system is not convenient. The DB service can be used to solve the issue as it is capable of buffering bundle data and then transforming the changes back on the flash file system.

Buffering of stored data into RAM is enabled by setting the mbs.db.buffered system property to true prior to DB bundle startup.

To explicitly save the current bundle data from the main DB onto the flash persistent storage, you can call the save method of the DB service's.

The save operation is automatically executed over a timeout period set with the mbs.db.savetime system property.

DB Manager Service

Keeping data of all bundles in a common database may lead to significant losses if the framework and its storage crash. The DB Manager service, registered in the Standard Version of the framework, allows for:

Managing Custom DBs

Each custom database is associated with a root directory and an alias. The root directory specifies the physical location of the database while the alias represents it in an user-friendly manner.

The DB Manager service is published under the com.prosyst.mbs.services.db.DBManager interface name. To create or access a custom database, use the appropriate getCustomDB method.

Tip: You can access the main DB as a custom DB under the DBManager.MAIN_DB ("<main>") alias.

Saving the Main DB

The DB Manager can be requested to save the main DB by calling its save method. See "Compromising between Speed and Persistence" for more details on the save operation.


DB Example

The following example illustrates the usage of DBs, provided by the DB Bundle. The example consists of two parts - one for writing data and one for reading data.

The involved database represents a phonebook with separate nodes for the letters with which the family names of entered people start.

The phonebook DB is created through the DB Manager as a custom one with alias and root directory according to the system properties my.db.alias and my.db.root respectively.

Writing Data

Listing 1.1 contains the activator of a bundle, which when started contacts the DB Manager to create the phonebook database and populates the database with entries in its fillDb method.

import java.io.File;
import java.io.IOException;
 
import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference;
import com.prosyst.mbs.services.db.DB; import com.prosyst.mbs.services.db.DBManager;
public class MyCustomDB implements BundleActivator { ServiceReference dbMngrRef = null; DBManager dbMngr = null; static String dbAlias = null; static String dbRoot = null; private DB aliasRootDb;
// Constants for DB entry keys
private static final String PHONEBOOK_KEY = "Phonebook"; private static final String LETTER_A = "A"; private static final String LETTER_B = "B"; private static final String LETTER_C = "C"; private static final String LETTER_J = "J"; private static final String LETTER_L = "L"; private static final String LETTER_S = "S";
// Methods inherited from BundleContext
public void start(BundleContext bc) throws Exception { dbMngrRef = bc.getServiceReference(DBManager.class.getName()); if (dbMngrRef != null) { dbAlias = System.getProperty("my.db.alias", "mydb"); dbRoot = System.getProperty("my.db.root", "mydb_root"); dbMngr = (DBManager) bc.getService(dbMngrRef); // (Re)Creating the phonebook DB
aliasRootDb = dbMngr.getCustomDB(dbAlias, new File(dbRoot)); fillDb(aliasRootDb); } }
public void stop(BundleContext bc) throws Exception { if(aliasRootDb != null) { dbMngr.ungetCustomDB(aliasRootDb); } if (dbMngrRef != null) {
bc.ungetService(dbMngrRef); } }
// Writes entries in the phonebook
private void fillDb(DB db) throws IOException { // Writing a main node for phone numbers
db.setValue(new String[] { PHONEBOOK_KEY },
null,
DB.NODE); // Creating a node for a latter
db.setValue(new String[] { PHONEBOOK_KEY, LETTER_A },
null,
DB.NODE); // Writing an entry for a person's phone number
db.setValue(new String[] { PHONEBOOK_KEY, LETTER_A, "Mr. R. Armstrong" }, "2121 4343", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_B },
null,
DB.NODE); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_B, "Mr. A. Been" }, "9988 9988", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_C },
null,
DB.NODE); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_C, "Mr. P. Cage" }, "1234 1234", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_C, "Mrs. L. Coldwell" }, "1212 3434", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_J },
null,
DB.NODE); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_J, "Mrs. A. Johnes" }, "7788 9977", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_J, "Mr. B. Johnes" }, "8899 8899", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_J, "Mrs. T. Johnson" }, "8765 4321", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_L },
null, DB.NODE); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_L, "Mrs. D. Lopez" }, "1234 5678", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_L, "Mr. M. Lopez" }, "6789 9876", DB.STRING); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_S },
null,
DB.NODE); db.setValue(new String[] { PHONEBOOK_KEY, LETTER_S, "Mr. B. Simpson" }, "8765 4321", DB.STRING); } }
Listing 1.1: Storing data in a DB.

Reading Data

Listing 1.2 contains again a bundle activator, which on starting the bundle refers to the phonebook through the DB Manager and reads the entries under the "J" letter. Data is retrieved by using both reading approaches - the readFromDB method calls directly the supplied DB instance, and the readNodeByNode one checks all nodes by operating with their DBNode representations.

import java.io.File;
import java.io.IOException;
 
import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.ServiceReference;
import com.prosyst.mbs.services.db.DB; import com.prosyst.mbs.services.db.DBManager; import com.prosyst.mbs.services.db.DBNode;
public class MyDBReader implements BundleActivator { String dbName = DB.class.getName(); ServiceReference dbRef = null; DBManager dbMngr = null; DB db = null; private static String[] key = {"Phonebook", "J"};
// Methods inherited from BundleContext
public void start(BundleContext bc) throws Exception { String dbAlias = System.getProperty("my.db.alias"); String dbRoot = System.getProperty("my.db.root"); if ((dbAlias == null) | (dbRoot == null)) { throw new BundleException("My DB system properties are not specified!"); } dbRef = bc.getServiceReference(DBManager.class.getName()); if (dbRef != null) { dbMngr = (DBManager) bc.getService(dbRef); // Getting the phonebook DB
db = dbMngr.getCustomDB(dbAlias, new File(dbRoot)); // Reading data
readFromDB(); readNodebyNode(); } }
public void stop(BundleContext bc) throws Exception { if (db != null) { dbMngr.ungetCustomDB(db); } if (dbRef != null) { bc.ungetService(dbRef); } }
// Reads data by using DBNode objects
private void readNodebyNode() throws IOException { System.out.println("[My DB Reader] Reading node by node ..."); // Getting the node for letter "J"
DBNode node = db.getNode(key); if (node != null && node.isValid()) { try { db.lock() // Getting the first name starting with "J"
DBNode child = node.firstChild(); if (child != null) { System.out.println(
"[My DB Reader] Entry under J: " + child.getName()
+ "\t->\t" + child.getValue()); // Getting the next name starting with "J"
DBNode next = child.next(); while (next != null) { System.out.println(
"[My DB Reader] Entry under J: " + next.getName()
+ "\t->\t" + next.getValue()); next = next.next(); } } } finally { db.unlock();
} } } // Reading data by using directly the DB object private void readFromDB() throws IOException { System.out.println("[My DB Reader] Reading directly from DB ..."); // Getting all names starting with "J"
String[] names = db.list(key, DB.KEY); for (int i = 0; i < names.length; i++) { String[] tmp = new String[key.length + 1]; System.arraycopy(key, 0, tmp, 0, key.length); tmp[tmp.length-1] = names[i]; // Getting the value corresponding to the specified name
String value = (String) db.getValue(tmp); System.out.println("[My DB Reader] Entry under J: " + names[i]
+ "\t->\t" + value); } } }
Listing 1.2: Reading from a DB.


System Properties

The following system properties can influence the runtime operation of the DB Bundle:

System Property Default Value Description
mbs.db.buffered true Turns on/off buffering DB data in RAM.
mbs.db.maxBuffers1 0

If this number of buffers are full, when save is done internally a pooled thread will be started to store them even in the case of framework startup. Value of 0 implies no limit of the buffer number.

This property has meaning only if mbs.db.buffered is true.

mbs.db.maxBuffers2 0

If this number of buffers are full, when "save" is called by a custom bundle the operation will be performed in the context of the user thread even in the case of framework startup. Value of 0 implies no limit of the buffer number.

This property has meaning only if mbs.db.buffered is true.

mbs.db.singleDRF false

To support non-blocking reading executed concurrently with a write operation to the same database, the DB Bundle uses a separate random access file pointing to the same data for each operation. However, on some JVM, such as J9 for Symbian, this approach may cause an exception when attempting to read data - to avoid such a fault, set this property to true. This will make the DB Bundle use a single remote access file for both the read and write operations.

This property has meaning only if the DB Bundle runs on the Standard Version of the framework.

mbs.db.maxL 0 The maximum size of a database including data and index.
mbs.db.savetime 10

Indicates the timeout in seconds for saving data from the main database.

This property has meaning only if mbs.db.buffered is true.

mbs.dbmanager.singleDB true If true, custom databases will be stored in the main DB.
mbs.db.debug false Turns on/off logging debug information about the DB Bundle operation.
mbs.db.console false Turns on/off printing debug information to the system output.
mbs.db.writeDebug.stackTrace false Turns on/off printing of stack traces for every write operation by the user.

To use the above system properties successfully, you should specify the properties:

For more information on using system properties in mBS's framework, refer to the "System Properties" document from "Getting Started".

Tip: As the DB Bundle is classified as system, to be able to restart it you should first enable operations on system bundles with the fw.sysmode console command.


References


System Bundles