Chapter 2. Introduction

2.1. Overview

The two components in JBossCache, generic cache (Cache implementation) and POJO cache ( PojoCache implementation), are both in-memory, transactional, replicated, and persistent. However, Cache is used as a generic cache system only. That is, it directly stores the object references and has a HashMap-like Api. As a result, it is fairly intuitive and easy to use for local or distributed caching. Nonetheless, it has the following constraints:

  • If replication or persistency is needed, the object will then need to implement the Serializable interface. E.g.,
    public Class Foo implements Serializable
  • Users will have to manage the cache specifically, e.g., when an object is updated, a user will need a corresponding API call to update the cache content.
    value = new Foo();
    cache.put(fqn, key, value);
    value.update(); // update value
    cache.put(fqn, key, value); // Need to repeat this step again to ask cache to persist or replicate the changes
  • If the object size is huge, even a single POJO field update would trigger the whole object to serialize. Thus, it can be unnecessarily expensive.
    value = new Foo();
    cache.put(fqn, key, value);
    value.update(); // update value
    cache.put(fqn, key, value); // This replicates the whole Foo instance
  • The object structure can not have a graph relationship. That is, the object can not have sub-objects that are shared (multiple referenced) or referenced to itself (cyclic). Otherwise, the relationship will be broken upon serialization (e.g., when replicate each parent object separately). For example, Figure 1 illustrates this problem during replication. If we have two Person instances that share the same Address , upon replication, it will be split into two separate Address instances (instead of just one). The following is the code snippet using Cache that illustrates this problem:
    joe = new Person("joe");
    mary = new Person("mary");
    addr = new Address("Taipei");
    joe.setAddress(addr);
    mary.setAddress(addr);
    cache.put("/joe", "person", joe);
    cache.put("/mary", "person", mary);
    

Illustration of shared objects problem during replication

Figure 2.1. Illustration of shared objects problem during replication

PojoCache, on the other hand, is a fine-grained "object-oriented" POJO cache. By "object-oriented", we mean that PojoCache provides tight integration with the object-oriented Java language paradigm, specifically,

  • no need to implement Serializable interface for the POJOs. Instead, POJO instrumentation during compile or load time is needed (see later for details).
  • replication (or even persistency) is done on a per-field basis (as opposed to the whole object binary level). This is the fine-grained replication vs. coarse-grained one in a generic cache library like Cache.
  • the object relationship and identity are preserved automatically in a distributed, replicated environment. It enables transparent usage behavior and increases software performance.
  • Once the POJO is attached to the cache system, all subsequent POJO operation will trigger the replication or persistency automatically.
    POJO pojo = new POJO();
    pojoCache.attach("id", pojo);
    pojo.setName("some pojo"); // This will trigger replication automatically.
    

In PojoCache, these are the typical development and programming steps:

  • Declare POJO to be "prepared" (in Aop parlance) either through an external xml file (e.g., jboss-aop.xml or *-aop.xml with the name of your choice), or through annotation inside the POJO. Depending on your preference, you can either pre-instrument your POJO (compile time) or have JBoss Aop do it at load time.
  • Use attach api to put your POJO under cache management.
  • Operate on POJO directly. Cache will then manage your replication or persistency automatically and transparently.
  • During your business logic, you can detach the POJO from the cache system such that any additional POJO operation won't be intercepted by the cache. You use detach either for external processing or you can mark the POJO for VM garbage collection.

More details on these steps will be given in later chapters.

PojoCache offers similar functionality to that of Cache at POJO level, specifically, transaction, replication, and passivation. For example, when you operate on a POJO (say, pojo.setName()) under a transaction context, it will participate in the transaction automatically. When the transaction is either committed or rolled back, your POJO operations will act accordingly (either commit or rollback).

PojoCache currently uses Cache as the underlying state replication system for POJO fields. As a result, the overall caching behavior is configured through the core Cache system, e.g., via *-service.xml. Furthermore, it also provides a way to obtain a Cache instance directly such that caller can operate on just plain cache APIs. For example, a user can use the Cache API [e.g., get(String fqn) and put(String fqn, String key, String value) ] to manage the cache states. Keep in mind again that your POJO is managed as a generic cache system in this case.

2.2. Features

Here are the current features and benefits of PojoCache:

  • Fine-grained replication. The replication mode supported is the same as that of the Cache implementation: LOCAL, REPL_SYNC, REPL_ASYNC, INVALIDATION_SYNC, and INVALIDATION_ASYNC (see the main JBossCache reference documentation for details). The replication level is fine-grained and is performed automatically once the POJO is mapped into the internal cache store. When a POJO field is updated, a replication request will be sent out only to the node corresponding to that modified attribute (instead of the whole object). This can have a potential performance boost during the replication process; e.g., updating a single key in a big HashMap will only replicate the single field instead of the whole map!

  • Transaction. The POJO operation can participate in a user transaction context. Upon user rollback, it will rollback all POJO operations as well. For example,

    POJO p = new POJO();
    p.setName("old value");
    pojoCache.attach("id", p);
    tx.begin(); // start a user transaction
    p.setName("some pojo");
    tx.rollback(); // this will cause the rollback
    p.getName(); // is "old value"
    

    Note that the transaction context only applies to the node level though similar to the Java semantics. That is, in a complex object graph where you have multiple sub-nodes, only the nodes (or fields) accessed by a user are under transaction context. To give an example, if I have a POJO that has field references to another two POJOs (say, pojo1 and pojo2). When pojo1 is modified and under transaction context, pojo2 is not under the same transaction context. So you can start another transaction on pojo2 and it will succeed.

    In addition, fine-grained operation (replication or persistency) under transaction is batched. That is, the update is not performed until the commit phase. And if it is rolled back, we will simply discard the modifications.

  • Passivation. PojoCache supports passivation that can passivate any underlying POJO value. By passivation of a node, we mean when it passivates, the node is evicted from in-memory and stored into a backend cache store of user's choice. When the node is accessed again, it will be retrieved from the store and put into memory. The configuration parameters are the same as those of the Cache counterpart. To configure the passivation, you will need to configure both the eviction policy and cacheloader.

  • Object cache by reachability, i.e., recursive object mapping into the cache store. For example, if a POJO has a reference to another advised POJO, PojoCache will transparently manage the sub-object states as well. During the initial attach() call, PojoCache will traverse the object tree and map it accordingly to the internal Cache nodes. This feature is explained in full details later.

  • Object reference handling. In PojoCache, multiple and recursive object references are handled automatically. That is, a user does not need to declare any object relationship (e.g., one-to-one, or one-to-many) to use the cache. Therefore, there is no need to specify object relationship via xml file.

  • Automatic support of object identity. In PojoCache, each object is uniquely identified by a String id. Client can determine the object equality through the usual equal method. For example, an object such as Address may be multiple referenced by two Persons (e.g., joe and mary). The objects retrieved from joe.getAddress() and mary.getAddress() should be identical.

    Finally, a POJO can be stored under multiple ids in the cache as well, and its identity is still preserved when retrieved from both places (after replication).

  • Inheritance relationship. PojoCache preserves the POJO inheritance hierarchy after the object item is stored in the cache. For example, if a Student class inherits from a Person class, once a Student object is mapped to PojoCache (e.g., attach call), the attributes in base class Person is "aspectized" as well.

  • Support Collection classes (e.g., List, Set, and Map based objects) automatically without aop instrumentation first. That is, you can use them either as a plain POJO or a sub-object to POJO without declaring them as "aspectized". In addition, it supports runtime swapping of the proxy reference as well. Details are described later.

  • Support pre-compiling of POJOs. The latest JBossAop has a feature to pre-compile (called aopc, so-called compile-time mode in JBossAop) and generate the byte code necessary for AOP system. By pre-compiling the user-specified POJOs, there is no need for additional declaration file (e.g., jboss-aop.xml ) or specifying a JBossAop system classloader. A user can treat the pre-generated classes as regular ones and use PojoCache in a non-intrusive way.

    This provides easy integration to existing Java runtime programs, eliminating the need for ad-hoc specification of a system class loader, for example. Details will be provided later.

  • POJO needs not implement the Serializable interface.

  • Support annotation usage. Starting from release 2.0, PojoCache also supports declaration of POJO through annotation under JDK5.0. As a result, there will be no need for jboss-aop.xml file declaration for POJOs, if annotation is preferred.

  • Ease of use and transparency. Once a POJO is declared to be managed by cache, the POJO object is mapped into the cache store behind the scene. Client will have no need to manage any object relationship and cache contents synchronization.

2.3. Usage

To use PojoCache, you obtain the instance from the PojoCacheFactory by supplying a config file that is used by the delegating Cache implementation. Once the PojoCache instance is obtained, you can call the cache life cycle method to start the cache. Below is a code snippet that creates and starts the cache:

String configFile = "replSync-service.xml";
boolean toStart = false;
PojoCache pcache = PojoCacheFactory.createCache(configFiel, toStart);
pcache.start(); // if toStart above is true, it will starts the cache automatically.
pcache.attach(id, pojo);
...
pcache.stop(); // stop the cache. This will take PojoCache out of the clustering group, if any, e.g.

2.4. Requirement

PojoCache is currently supported on JDK50 (since release 2.0). It requires the following libraries (in addition to jboss-cache.jar and the required libraries for the plain Cache) to start up:

  • Library:

    • pojocache.jar. Main PojoCache library.
    • jboss-aop-jdk50.jar. Main JBossAop library.
    • javassist.jar. Java byte code manipulation library.
    • trove.jar. High performance collections for Java.