IBM Research
 

WBI Persistence

With the WBI Development Kit version 4.4 a new persistence subsystem has been introduced. The new persistence mechanism retains a similar, though simplified, API to the previous version. However, it has been extended to support hierarchical organization of data. This means any Section or node can contain other Sections, much like a directory on a filesystem can contain other directories.

The persistent subsystem also supports the plugging in of different backends using databases like SQL, LDAP, etc. The provided backend uses the filesystem. More information about writing your own backend is available.

Table of Contents

  1. A simple example
  2. Porting from the Previous API
  3. Using hierarchical functionality
  4. Saving raw objects
  5. Don't Try Fit a Round Peg into a Square Hole

A simple example

The plugin developer point-of-entry into the persistence subsystem is with the getHomeSection() method of Plugin. For typical configuration purposes, this is essentially all you need to know. For example, one might set up a debugging flag as follows:

public class MyPlugin extends HttpPlugin {
  private enableDebugging;
  public void initialize() {
    enableDebugging = 
      getHomeSection().getBooleanValue("debugging", false);
    // ...
  }
  // ...
}

The getHomeSection() method returns a section based upon the name of your plugin. This means if you rename your plugin, any persistent data will no longer be available. Likewise if you develop a second plugin with the same name as the first, the data will still be there. Please do not rely on the actual layout or location of the data on the physical filesystem. You might, however, need to know this in order to set the debugging flag in the example. Let's say this plugin was called "myplugin", the file accessed by getHomeSection() would be $WBIHOME/etc/plugins/myplugin/home.prop. While you might need to know these locations for your own development, please do not rely on them programmaticly -- i.e., make sure you plugin just uses the getHomeSection() API call, as the physical locations might change in a future release. It is also suggested that if you create an administration page for your plugin which can set and save configuration values if you anticipate others using it.

The argument to getSection() or createSection() is a "/"-delimited path to the desired section. Just like with a filesystem, a path which is preceded by a "/" is absolute. Please be careful not to precede any of the paths you pass to these methods with a "/", otherwise your data will "escape" from the plugin namespace in which it belongs. Also, the location and behavior of the various WBI configuration data you will find up there is to be considered undocumented, and it may change from release to release.

For example, a section with the path "http://ibm.com/index.html" will create the following three nested sections "http:" / "ibm.com" / "index.html", which is probably not what you intended. You should consider using java.net.URLEncoder to encode the "/"'s.

As with the previous system, the second argument to getBooleanValue() is a default value to use if a value is not found.

Porting from the Previous API

While the API is similar to the previous one, there are significant changes and you will need to modify your code to compile with the new API.

  1. Replace import com.ibm.pvccommon.util.* with import com.ibm.wbi.persistent.*

  2. Replace DatabaseSection with Section.

  3. Replace getSystemContext().getSystemDatabase().getSection() with getHomeSection().

  4. None of the methods throw exceptions anymore. If an error occurs, a value of null is returned and an error is printing to the trace logs.

  5. Replace getSystemContext().getSystemDatabase().saveSection(section); with section.save().

  6. The setTYPEValue() methods have been removed as they are extraneous. Simply remove the TYPE part of those method names.

  7. Database.createSection(sectionName, subsetName) becomes section.createSection(sectionName).

  8. The exists() method was replaced by keyExists(). To find out whether a section exists, use sectionExists().

  9. There are two methods for getting an enumeration of contained sections: sections() and sectionsRecursively(). Sections() will only get the immediately contained sections of a node. sectionsRecursively() will get all contained sections under a node. I.e., rootSection.sectionsRecursively() will return all sections in the database. The order in which the sections are returned by either method is not defined.

  10. Some sections might have no key/value pairs. The way to find out if this is the case is to call keys() and hasMoreElements() on the returned enumeration. Likewise a section that contains no other sections (a leaf) can be detected with sections() and hasMoreElements() called on the returned enumeration.

Using hierarchical functionality

The hierarchical extensions to the API allow the developer to organize data in a tree. For example, a plugin might have some configuration settings and may want to save other application-specific data. Let's say you're writing a plugin to keep track of the places you've visited on the web. You might want to keep a record of data associated with each URL encountered. That record might contain the number of visits and the last visit date for now, though you think you'll be extending that in the future. One might use the persistence API to save this kind of data as in the following example:

public class MyPlugin extends HttpPlugin {
  String foo, bar;  // configuration options
  Section home;
  public void initialize() {
    home = getHomeSection();
    foo = home.getValue("foo");
    bar = home.getValue("bar");
 
    // ...
    Monitor m = new MyMonitor();
    m.setup("my monitor", "content-type=text/html", 10);
    addMeg(m);

    // ...
  }

  class MyMonitor extends HttpMonitor {
    // ...
    public void handleRequest(RequestEvent e) {
      DocumentInfo di = (DocumentInfo)e.getRequestInfo();
      String url = di.getUrl();
      // we want to create a new section if necessary, 
      // so use createSection(), not getSection()
      Section s = home.createSection(URLEncoder.encode(url));
      int visits = s.getIntegerValue("visits", 0) + 1;
      s.setValue("visits", visits);
      s.setValue("lastvisit", new Date().getTime());
      s.save();
    }
  }

  // ...
}

Note that in addition to illustrating hierarchical functionality, this example introduced the save() method. Save() will commit any unsaved data in the Section on which it is called and its contained sections to persistent storage.

Saving Raw Objects

The setValue() method is overloaded to take any java primitive type as well as any Object which implements Serializable. If the argument is non-String Object, it will be stored using the java serialization mechanism. While simple and convenient for some purposes, there are some issues introduced by using this feature.

  • The resulting data file will not be human readable, and will be subject to problems when the serialized object goes through code revisions.
  • As an optimization, the persistence system uses a dirty flag which is set to true when setValue() is called, and false when save() is called. If you were to set a key to a container object like a Vector, the persistence system has no way to know that values within that Vector have changed. In such a case, you might want to use the version of save() which takes the boolean argument true to force the save to occur regardless of the state of the dirty flag.

Don't Try to Fit a Round Peg into a Square Hole

The persistence system is intended first and foremost as a simple system for storing configuration settings. While it is possible to store larger amounts of data with it, or to write a more scalable backend, please keep in mind that what you may really want is something like JDBC.

[Back to Table of Contents]