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:
- Main DB - Holds the data of every bundle using it in a separate root, whose data
can be accessed directly only by the bundle owning it. Other bundles can only read the
content of the bundle-specific nodeand only in case they access the main DB as custom with alias "<main>". See "DB Service"
for more information on the behavior of the main DB.
- Custom DB - Holds data, which can be structured in a custom manner and can
be accessed and modified by any bundle if known the database alias and/or root directory. See "DB
Manager" for more information on custom DBs.
Supported Data Types
With the DB Bundle bundles can store data of the following types:
String
Integer
Short
Long
Boolean
Byte
Character
Float
Double
- Implementations of the
com.prosyst.util.io.Externalizable interface
- Arrays of the types above except for
Externalizable
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 calling methods of the corresponding
DB instance. This is
possible only for custom DBs.
- By browsing through the nodes of the DB. This is valid for both main
and custom DBs.
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:
- Get the value of the associated entry by using the
getValue
method, which suits best the application's requirements.
- Go to the node's first child and then iteratively to its other children
or to the other nodes from the same level. To switch to a node's first child,
use the
getFirstChild method. To go to the next entry from the
same level, call the next method.
- Go to the entry's parent by invoking the
parent method.
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:
- Usage of custom databases placed at different locations
- Writing data on a volatile file system with fast write access
and then transferring it altogether to a persistent storage.
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.
- If a custom DB is created with specified alias and root directory, the DB
Manager will provide the proper DB instance only if the
getCustomDB(String alias, File root) method is called with both the alias and the root.
- If a custom DB is created only with specified alias, the DB Manager will
keep data in its storage area within a separate directory named after the
alias. The DB can be then requested only by using
getCustomDB(String
alias) method.
- If a custom DB is created only with specified root directory, the DB Manager
will return the DB only if the
getCustomDB(File root) method
is called with a File instance representing the same root directory.
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:
- In the default.prs file before the framework is started.
- At framework runtime through the set console command or mConsole
before the DB Bundle is started. Otherwise, you have to restart the bundle
to save the changes.
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