JBossCache as a POJO Cache
Description: JBossCache is an in-memory, replication, transaction, and fine-grained cache system.
Keywords: JBoss Cache, JBossCacheAop, POJO cache, Ben Wang, cache, clustering, replication, transactional, persistent
Date: October, 2005

## JBoss Cache as a POJO Cache

by Ben Wang
11/2005

In-memory caching is a crucial feature in today's large-scale enterprise applications, where scalability and high performance are required. An in-memory cache can store either application state information (e.g., a `HttpSession` in a web application) or database query results (i.e., entity data). Since many enterprise applications run in clustered environments, the cache needs to be replicated across the cluster. Furthermore, if more reliability is needed, the in-memory cache should also be persisted to the hard disk or database.

Most existing in-memory caching solutions fall into the category of what we call "plain" cache system, in which the direct object references are stored and cached. Since a plain cache deals with the object reference directly, it acts like an elaborate `HashMap` and thus is very intuitive to use. When an object needs to be replicated or persisted in a plain cache system, the object has to implement the `Serializable` interface. However, a plain cache also has some known limitations regarding to replication or persistency:

- User will have to manage the cache specifically. For instance, when an object is updated, a user will need to execute a corresponding API to update the cache content.
- The need for Java object serialization could hamper performance. If the object size is huge, even a single field update would trigger serialization of the whole object and replication across the cluster. Thus, it can be unnecessarily expensive.
- Java object serialization cannot preserve the relationship between cached objects. In particular, the cached object cannot be referenced multiple times by other objects (multiple referenced), or have an indirect reference to itself (cyclic). Otherwise, the relationship will be broken upon serialization. For example, Figure 1 illustrates this problem during replication. If we have two `Person` instances that share the same `Address` object, upon replication, it will be split into two separate `Address` instances (instead of one).

Figure 1. Plain cache does not preserve object relationship during serialization

Addressing the above problems in plain cache system, there is another new category of cache system: POJO (plain old Java object) cache. A POJO cache is a system that acts as an "object-oriented" and distributed cache. In this system, once a user attaches the POJO to the cache, the caching aspect (e.g., replication and persistence) should be transparent to the user. A user would simply operate on the POJO without worrying about updating the cache content or maintaining the object relationship. There is no explicit API called to manage the cache. In addition, it has three more characteristics:

- There is no need to implement the `Serializable` interface for the POJOs.
- Replication (or even persistence) is done on a per-field basis (as opposed to the whole object binary level in plain cache) resulting a potential boost to performance.
- The object relationship and identity are preserved automatically in a distributed replicated environment. This enables transparent usage experience and increases software performance.

A leading in-memory POJO cache solution is the open-source JBoss Cache . JBoss Cache is the first Java library that supports replicated, persistent, transactional, and fine-grained caching. It can be used both as a POJO cache and a plain cache. Since the JBoss Cache is 100% Java-based, it runs in any Java SE environment, including inside an application server or as a standalone process. JBoss Cache has been used inside the JBoss Application Server for the EJB 3.0 stateful session bean clustering and http session replication, for example.

In this article, we demonstrate how JBoss Cache can be used as a POJO cache (through its `JBossCacheAop` component). A use case will also be given to illustrate key features in a distributed setting.

### Brief JBoss Cache Overview

#### Plain cache

The default plain cache module in JBoss Cache is called `TreeCache`. You can configure it either programmatically or through an external xml file. Here are the features that you can configure:

1. Cache mode: It can be either local or replicated. If it is replicated, you can further specify synchronous or asynchronous modes.
2. TransactionManager: You can specify a JTA compliant transaction manager for JBoss Cache to lookup. If it finds an ongoing transaction context, it will participate in that transaction and perform commit or rollback accordingly.
3. Pluggable eviction policy: The cache eviction policy refers to the algorithm the cache uses to expire its contents. In JBoss Cache, you can implement your own eviction policy via a pluggable interface. JBoss Cache currently ships with a region-based `LRUEvictionPolicy`.
4. Isolation level: JBoss Cache uses JDBC-style semantics for locking behavior. Instead of specifying read/write locks specifically, you can specify different isolation levels for ease of use.
5. Pluggable CacheLoader: CacheLoader allows you to load persisted cache contents back into the memory. JBoss Cache currently supports file, SleepyCat, and JDBC based loaders.
6. Overflowing: Combined with a cache loader and an eviction policy, this provides the passivation/activation feature seen in EJB. Whenever an item is evicted, it will be passviated so it is always persistent.

#### POJO cache

The POJO cache module in JBoss Cache is called `PojoCache`. In order to use POJO cache, you have to "prepare" the objects (this process is also known as object instrumentation) before they are cached. This is needed so the system can intercept the POJO operations. The object instrumentation process is performed by the JBoss AOP library. JBoss AOP allows you to specify the to-be-instrumented classes via an XML file or annotations. Currently, we support only the JDK 1.4 style annotation (a specific feature of JBoss AOP). Note that JDK 5.0 annotation support is coming in the next release and it will make the instrumentation process nearly transparent!

`PojoCache` is a sub-class of `TreeCache`, so it uses the same XML file for configuration and provides the same caching functionality as its super class counterpart. The JBoss POJO Cache also has a POJO-based eviction policy.

### A Use Case Walk Through

In the rest of the article, we will use a "sensor network supervising system" example to illustrate the capability of JBoss POJO Cache in providing instantaneous fine-grained state replication and automatic preservation of state object relationship.

#### Problem description

Along a high-speed railway, in the different stations there are thousands of sensors that need to be monitored and supervised. Examples of such instruments include temperature, wind and rain sensors that are critical to the successful operation of a high-speed train. If a particular sensor is malfunctioning, the manager computer in the supervising system should alert the administrator, shutdown the unit, and/or schedule it for maintenance.

Since the operation is mission critical, the supervising system has to be highly available, meaning that whenever one sensor manager computer in the system goes down, the administrator should be able to seamlessly switch over to another one to perform supervision and active management. Hence, all manager computers must replicate the same sensor network state information at real time. Note that the characteristics of this kind of system, i.e the requirement of high-availability and the existence of thousands (or even larger numbers) of elements, are also commonplace elsewhere in modern-day network management. Figure 1 illustrates the overview of such system that includes a clustering capability.

Figure 1. Overview for the sensor supervising system

Because of the hierarchical nature of the sensor network, a complicated domain object graph will typically be required to model the sensor network on the manager side. When the domain object states are not replicated (or persisted), management of object relationships (e.g., adding nodes and traversing the graph nodes) is provided by the JVM itself, and thus is transparent to the end user. However, because the Java serialization process does not recognize the object relationship, this seemingly simplistic object graph relationship will break down when the states are either replicated or persisted. As a result, it renders a simple failover of the manager side components difficult to achieve.

Traditionally, to provide complete failover (or persistence) capability, the system has to be designed to manage the object relationship explicitly, a la modern day object-relation mapping (ORM) solution approach. And in a traditional entity persistence layer-style design you have to do the following:

- Externalize the relationship mapping (e.g., in a xml file)
- Breakdown the object construct individually into primitive types
- Have a notion of object identity to preserve the relationship
- Keep track of dirty fields (if you want to avoid the whole object serialization cost)
- And, finally, assemble the primitive types back into objects

By using the JBoss POJO Cache to handle your object states, however, none of the problems mentioned above exists! To elaborate, the benefits of using it are as follows:

- Instantaneous failover (through state replication) without modifying your domain object model or specifying complicated object relational mapping.
- Optional state persistency that can preserve the object graph as well.
- Fine-grained replication (or persistency) that can be batched for optimized network traffic.

As we described above, the replication and/or persistence aspects in the POJO cache are totally transparent to the user. Note that for total clustering, there is another aspect of load-balancing or locating the primary and/or secondary managers to which both the client GUI (for administrators) and the sensors should connect. We do not cover such issues here and will focus only on the replication of the sensor network state across the managers.

#### Topology and Object modeling

Figure 2 is the topology for our sensor supervising system example. Basically, in this simplified example, we have two stations (`Tokyo` and `Yokohama`), and within each station we will have one wind and one rain sensor, respectively. For each sensor, there are numerous components that need to be supervised, e.g. the power supply and the sensor unit itself. Our goal is to supervise the sensors efficiently by 1) monitoring the individual item status (e.g., a `StateItem`) as well as their overall status (e.g., `Wind Summary`), and 2) having the ability to bring up and down individual sensors at runtime without restarting the clustered manager nodes.

Figure 2. Topology for the sensor supervising system

Figure 3 illustrates this domain model using a class diagram. Essentially, we have a `PropagationManager` at the top level that handles the whole sensor network. It always has one root `Node`. Then the root `Node` can have recursively numerous `Node`s. Each `Node` can have multiple `StateItem`s. A `StateItem` is the smallest management unit, for example, representing the unit's power supply.

Figure 3. Class diagram for the sensor supervising system

For the current topology in Figure 2, `Japan` represents the root node, while `Tokyo` and `Yokohama` are the station nodes, respectively. The different sensors (e.g., rain or wind) are represented by a `Node` object as well. Each sensor then has two `StateItems`, namely `Power Supply` and `Sensor Unit`.

The relationship between `Node`s is bi-directional, e.g., we can navigate from the root node all the way to the leaf node, or we can navigate backwards through the parent node as well. In addition, there are one `WindSensor Summary` and one `RainSensor Summary` `StateItem` instances that reference the respective sensors as well. The purpose of the summary items is to monitor the overall health of the, say, wind and rain sensor units as a group. As a result, the objects in the object graph for the sensors are multiple referenced (as shown in Figure 2 and 3).

#### Configuration

Before we can use the POJO cache functionality, we will need to use JBoss AOP tools to instrument the POJOs (namely, `PropagationManager`, `Node`, and `StateItem` classes). We can do it via either XML declaration or annotation. Here we illustrate the POJO instrumentation via the JBoss AOP JDK 1.4 annotation (JDK 5.0 annotation will be supported in the next JBoss Cache release.) Below are the code snippets that declare the annotation on the three main interfaces.

    /**
    * @@org.jboss.cache.aop.InstanceOfAopMarker
    */
    public interface PropagationManager {
      public void setRootNode(String rdn);

      public void addNode(String parentFdn, String rdn);

      public void addStateItem(String parentFdn,
        long itemId, String name, long defaultState);

      public Node findNode(String fdn);

      public void stateChange(String fdn, long itemId,
        long newState);

    ...
    }

    /**
    *
    * @@org.jboss.cache.aop.InstanceOfAopMarker
    */
    public interface Node {
      public void addChildNode(Node child);

      public List getChildren();

      public void setParentNode(Node parent);

      public Node getParentNode();

      public void addStateItem(StateItem stateItem);

      public void setSummaryStateItem(StateItem stateItem);

      public StateItem getSummaryStateItem();

      public List getStateItems();

      public StateItem findStateItem(long itemId);

      public void setPropagationRule(PropagationRule rule);

    ...
    }

    /**
    * @@org.jboss.cache.aop.InstanceOfAopMarker
    */
    public interface StateItem {
      public long getItemId();

      public boolean setState(long state);

      public long getState();

      public void setName(String name);

      public String getName();

    ...
    }

Note the annotation inside the JavaDoc -- `@@org.jboss.cache.aop.InstanceOfAopMarker` is a JBoss POJO Cache annotation that essentially declares all instances of this interface will be instrumented (so there is no need to annotate individual classes). If you want to annotate a specific class (without propagating to its sub-class), you can also use the `@@org.jboss.cache.aop.AopMarker` annotation.

After the interfaces are annotated, we then use a JDK 1.4 style JBoss Aop annotation precompiler, `annoc`, and an aop precompiler, `aopc`, to perform compile time instrumentation. Once these steps are done, the instrumentation process is complete. And we are ready to run the example.

#### Code Snippet

Below is a code snippet that instantiates a `PropagationManager` instance and sets the appropriate relationship between the station and the sensor nodes. Finally, we use the `PojoCache` API `putObject()` to put the POJO (in this case, the `PropagationManager` instance) under cache management. After that, any POJO operation will be fine-grain replicated, e.g., a `setState()` operation will only replicate the corresponding state field (of which is an integer).

    protected void setUp() throws Exception {
      cache1_ = createCache("TestCluster");
      cache2_ = createCache("TestCluster");
      initPm();
    }

    protected void tearDown() throws Exception {
      cache1_.remove("/");
      cache1_.stop();
      cache2_.stop();
    }

    private PojoCache createCache(String name) throws
        Exception {
      // configure the cache through injection
      PropertyConfigurator config = new
        PropertyConfigurator();
      // read in the replSync xml.
      // Here we use synchronous mode replication.
      config.configure(tree, "META-INF/replSync-service.xml");
      // We can set a different cluster group.
      tree.setClusterName(name);
      tree.start(); // kick start the cache
      return tree;
    }

    /**
    * Populate the propagation tree.
    */
    protected void initPm() throws Exception {
      pm_ = new PropagationManagerImpl();

      pm_.setRootNode("Japan");

      pm_.addNode("Japan", "Tokyo"); // Tokyo station

      // Wind sensor device
      pm_.addNode("Japan.Tokyo", "WindSensor1");
      pm_.addStateItem("Japan.Tokyo.WindSensor1", 1000,
        "power supply", 1040); // power supply
      pm_.addStateItem("Japan.Tokyo.WindSensor1", 1001,
        "sensor unit", 1040); // sensor unit

      // rain sensor device
      pm_.addNode("Japan.Tokyo", "RainSensor1");
      pm_.addStateItem("Japan.Tokyo.RainSensor1", 1002,
        "power supply", 1040); // power supply
      pm_.addStateItem("Japan.Tokyo.RainSensor1", 1003,
        "sensor unit", 1040); // sensor unit

      pm_.addNode("Japan", "Yokohama"); // Yokohama station

      // wind sensor device
      pm_.addNode("Japan.Yokohama", "WindSensor2");
      pm_.addStateItem("Japan.Yokohama.WindSensor2", 1000,
        "power supply", 1040); // power supply
      pm_.addStateItem("Japan.Yokohama.WindSensor2", 1001,
        "sensor unit", 1040); // sensor unit

      // rain sensor device
      pm_.addNode("Japan.Yokohama", "RainSensor2");
      pm_.addStateItem("Japan.Yokohama.RainSensor2", 1002,
        "power supply", 1040); // power supply
      pm_.addStateItem("Japan.Yokohama.RainSensor2", 1003,
        "sensor unit", 1040); // sensor unit

      // summary node for wind sensors in this network
      pm_.createNode("WindSummary", "WindSummary");
      pm_.setUpperNode("WindSummary",
        "Japan.Tokyo.WindSensor1"); // assoication
      pm_.setUpperNode("WindSummary",
        "Japan.Yokohama.WindSensor2"); // association

      // summary node for rain sensor in this network
      pm_.createNode("RainSummary", "RainSummary");
      pm_.setUpperNode("RainSummary",
        "Japan.Tokyo.RainSensor1"); // association
      pm_.setUpperNode("RainSummary",
        "Japan.Yokohama.RainSensor2"); // association
    }

    /**
    * Main starting point. Called by main.
    */
    public void testPropagation() throws Exception {
      // Here we ask the pojo cache to manage pm
      cache1_.putObject("/monitor", pm_);

      // Output
      printStatus("Initial state", pm_);

      // Retrieve the pojo from the Manager #2
      PropagationManager pm2 = (PropagationManager)
        cache2_.getObject("monitor");

      System.out.println(
        "---------------------------------------------");
      System.out.println("Modified on Manager #1");
      // A state has been changed in one of the item.
      // This will be fine-grained replicated.
      pm_.stateChange("Japan.Tokyo.RainSensor1", 1003, 1041);
      printStatus("Japan.Tokyo.RainSensor1: id: 1003 state:
        1040->1041 (retrieved from Manager #2!)", pm2);

      System.out.println(
        "---------------------------------------------");
      System.out.println("Modified on Manager #2");
      // A state has been changed in one of the item.
      // This will be fine-grained replicated.
      pm2.stateChange("Japan.Yokohama.WindSensor2",
        1001, 1041); // Modified state on cache #2
      printStatus("Japan.Yokohama.WindSensor2: id: 1001 state:
        1040->1041 (retrieved from Manager #1!)", pm1);

      System.out.println(
        "---------------------------------------------");
      System.out.println(
        "Add a new VibrationSensor on Tokyo station");
      // Vibration sensor device
      pm_.addNode("Japan.Tokyo", "VibrationSensor1");
      pm_.addStateItem("Japan.Tokyo.VibrationSensor1", 1004,
        "power supply", 1040); // power supply
      pm_.addStateItem("Japan.Tokyo.VibrationSensor1", 1005,
        "sensor unit", 1040); // sensor unit
      printStatus("Japan.Tokyo.VibrationSensor1:
        (retrieved from cache #2)", pm2);
    }

    public static void main(String[] args) throws Exception {
      PropagationReplAopTest pmTest =
        new PropagationReplAopTest();
      pmTest.setUp();
      pmTest.testPropagation();
      pmTest.tearDown();
    }

In the above example snippet, please note the following key points:

1. The two cache instances are configured through XML injection and started thereafter. They can be run on two separate JVMs (and different physical machines).
2. A `PropagationManager` instance is put into cache management via a `putObject()` API call. After that, only pure POJO operations are needed. For example, we can add additional nodes to the `PropagationManager` instance and it will replicate accordingly.
3. We retrieve a fresh instance of `PropagationManager` on cache #2 via a `getObject` call.
4. Various `setState()` calls trigger only fine-grained replication to the other cache instance.

#### Result Output

Finally, when the example is run, the resulting output will printed as follows:

    Initial state
    ---------------------------------------------
    Japan (Summary : 2000 [ok])
    + Tokyo (Summary : 2000 [ok])
    + + WindSensor1 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1040)
    + + RainSensor1 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1040)
    + Yokohama (Summary : 2000 [ok])
    + + WindSensor2 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1040)
    + + RainSensor2 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1040)

    ---------------------------------------------
    Modified on Manager #1
    StateItem.setState(): id: 1003 state changed \
      from 1040 to 1041
    ---------------------------------------------
    Japan.Tokyo.RainSensor1: id: 1003 state: 1040->1041 \
       (retrieved from Manager #2!)
    ---------------------------------------------
    Japan (Summary : 2004)
    + Tokyo (Summary : 2004)
    + + WindSensor1 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1040)
    + + RainSensor1 (Summary : 2004)
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1041)
    + Yokohama (Summary : 2000 [ok])
    + + WindSensor2 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1040)
    + + RainSensor2 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1040)

    ---------------------------------------------
    Modified on Manager #2
    StateItem.setState(): id: 1001 state changed \
      from 1040 to 1041
    ---------------------------------------------
    Japan.Yokohama.WindSensor2: id: 1001 state: 1040->1041 \
       (retrieved from Manager #1!)
    ---------------------------------------------
    Japan (Summary : 2004)
    + Tokyo (Summary : 2004)
    + + WindSensor1 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1040)
    + + RainSensor1 (Summary : 2004)
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1041)
    + Yokohama (Summary : 2004)
    + + WindSensor2 (Summary : 2004)
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1041)
    + + RainSensor2 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1040)

    ---------------------------------------------
    Add a new VibrationSensor on Tokyo station
    StateItem.setState(): id: 1004 state changed from \
      2000 to 1040
    StateItem.setState(): id: 1005 state changed from \
      2000 to 1040
    ---------------------------------------------
    Japan.Tokyo.VibrationSensor1: (retrieved from cache #2)
    ---------------------------------------------
    Japan (Summary : 2004)
    + Tokyo (Summary : 2004)
    + + WindSensor1 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1040)
    + + RainSensor1 (Summary : 2004)
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1041)
     + + VibrationSensor1 (Summary : 2000 [ok])
       + + | ( name = power supply, id = 1004, state =1040)
       + + | ( name = sensor unit, id = 1005, state =1040)
    + Yokohama (Summary : 2004)
    + + WindSensor2 (Summary : 2004)
    + + | ( name = power supply, id = 1000, state =1040)
    + + | ( name = sensor unit, id = 1001, state =1041)
    + + RainSensor2 (Summary : 2000 [ok])
    + + | ( name = power supply, id = 1002, state =1040)
    + + | ( name = sensor unit, id = 1003, state =1040)

Please note the bold text lines. Basically, we do a POJO operation `setState` first on Manager #1 and then print out the propagation tree from the second manager to verify that the state has been updated, and vice versa. It is worthwhile to repeat that, although not shown in the output for the replication layer traffic, each `setState()` operation will only trigger a fine-grained field level replication. In addition, if the call is under a transaction context, then the update will be batched, e.g., replicated only when it is ready to commit. Finally, notice that we have the capability to add a new sensor on the fly into the network. A traditional system would have required some sort of restart mechanism.

If you are interested to run this example yourself, you can download JBoss Cache release 1.2.4. Check under the directory `examples/aop/sensor` that contains full source and instruction to run.

### Conclusion

In this article we have demonstrated the capability of JBoss Cache acting as a POJO cache by leveraging the `PojoCache` component. By using the POJO cache functionality, it provides seamless failover capability for POJOs by performing fine-grained replication while perserving object graph relationship.

### Acknowledgment

The author would like to acknowledge Mr. Yusuke Komori of SMG Co. from Japan for kindly contributing the use case in this article.

### References

1. JBoss Cache documentation
2. JGroups
3. JBoss AOP documentation