# TreeCacheAOP

### Ben Wang

`<ben.wang@jboss.com>`

### Bela Ban

`<bela@jboss.com>`

August, 2004

## 1. Introduction

The `JBossCache` component, `TreeCacheAop`, is a replicated and transactional "object-oriented" cache. By "object-oriented", we mean that TreeCacheAop provides tight integration with the object-oriented Java language paradigm, specifically it automatically : 1) manages object mapping and relationship, 2) provides support for inheritance relationship between "advised" POJOs (plain old Java object), and 3) preserves object identity, for a client under both local and replicated cache modes.

By leveraging the dynamic AOP capability in JBossAop, it is able to map a complex object into the cache store while preserving the object relationship behind the scene. During replication mode, it also performs fine-granularity (i.e., on a per-field basis) updates, and thus has the potential to boost cache performance and minimize network traffic.

`TreeCacheAop` extends the functionality of TreeCache to POJOs with dynamic AOP (Aspect-Oriented Programming)-enabled capability. That is, in addition to the TreeCache basic features such as transaction, replication, and eviction policy, `TreeCacheAop` also provides transparent object cache retrieval and update for user-specified POJOs (configured in a `jboss-aop.xml` file) through plain get/set methods associated with the POJOs.

`TreeCacheAop` employs the JBoss standalone AOP framework in the JBoss4.0 release to perform dynamic AOP interception. The framework provides declarative semantics (e.g., a `jboss-aop.xml` configuration) to "advise" POJOs. Once it is declared, a user will only need to initiate the transparent cache mechanism on that POJO by issuing a `putObject(String fqn, Object pojo)` method call first. After that, plain get/set methods from that POJO [e.g., `getName()`, `setName()`, etc], or direct field read and write operations, will be intercepted by the AOP framework, and the framework will, in turn, invoke the `org.jboss.cache.aop.CacheInterceptor` that will retrieve or update the object contents from the cache, respectively.

`TreeCacheAop` can also be used as a plain `TreeCache`. For example, if a POJO is not declared aop-enabled, a user will need to use the `TreeCache` API [e.g., `get(String fqn)` and `set(String fqn, String key, String value)`] to manage the cache states. Of course, users will need to consider the extra cost in doing this.

## 2. Feature

Here are the current features and benefits of TreeCacheAop:

- Replication, transaction, and eviction policy. All three aspects can be configured through a TreeCache/Aop configuration xml file. Please see the documentation of JBossCache for details.

- 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, `TreeCacheAop` will transparently manage the sub-object states as well. During the initial `putObject()` call, `TreeCacheAop` will traverse the object tree and map it accordingly to the internal TreeCache nodes. This feature is explained in full details later.

- Object reference handling. In TreeCacheAop, 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.

- Automatic support of object identity. In TreeCacheAop, each object is uniquely identified by an internal FQN. Client can determine the object equality through the usual `equal` method. For example, an object such as `Address` may be multiple referenced by two `Person`s (e.g., `joe` and `mary`). The objects retrieved from `joe.getAddress()` and `mary.getAddress()` should be identical.

- Inheritance relationship. TreeCacheAop preserves the POJO inheritance hierarchy after the object item is stored in the cache.

- Replication on modified attribute nodes only (as in per-field update basis), e.g., when an object is put into `TreeCacheAop` , a replication request will be sent out only when any attribute is modified and only the node corresponds to that modified attribute. This can have potential performance boost during replication process, e.g., update a single key in a big HashMap will only replicate one single field instead of the map!

- Support List, Set, and Map based objects automatically without declaring them as aop-enabled. That is, you can use them either as a plain POJO or a sub-object to POJO without declaring them as advisable.

- Support pre-compiling of POJOs. The latest JBossAop has a feature to pre-compile (called `aopc`) 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 TreeCacheAop 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. Please see the `Ant` build file (`build.xml`) under the standalone package for an example of pre-compiling.

- Ease of use. Once a POJO is declared to be managed by cache (i.e., putObject() call), the POJO object is mapped into the cache store behind the scene. Client will not need to manage any object relationship and cache contents synchronization.

## 3. Concepts

Following explains the concepts and top-level design consideration of TreeCacheAop.

### 3.1. Dynamic AOP interception

JBossAop provides a mechanism to add an interceptor at runtime. TreeCacheAop uses this feature extensively to provide user transparency. Every "advised" POJO class will have an associated `org.jboss.aop.InstanceAdvisor` instance. During a `putObject(FQN fqn, Object pojo)` operation (API explained below), TreeCacheAop will examine to see if there is already a `org.jboss.cache.aop.CacheInterceptor` attached. (Note that a `CacheInterceptor` is the entrance of TreeCacheAop to dynamically manage cache contents.) If it has not, one will be added to `InstanceAdvisor` object. Afterwards, any POJO field modification will invoke the corresponding `CacheInterceptor` instance. Below is a schematic illustration of this process.

The figures shown are operations to perform field read and write. Once a POJO is managed by cache (i.e., after a `putObject` method has been called), AOP will invoke `CacheInterceptor` automatically every time there is a field read or write. However, as you can see the difference between these figures, while field write operation will go to cache first before, eventually, invoke the in-memory update, the field read invocation does not involve in-memory reference at all, since the value in cache and memory should be synchronized. Instead, the field value from the cache is returned.

Figure 1. Dynamic AOP interception for field write

Figure 2. Dynamic AOP Interception for field read

### 3.2. Object mapping by reachability

A complex object by definition is an object that may consist of composite object references. Once a complex object is declared "prepare" (e.g., a `Person` object), during the `putObject(Fqn fqn, Object pojo)` operation, TreeCacheAop will add a `CacheInterceptor` instance to the `InstanceAdvisor` associated with that object, as we have discussed above. In addition, the cache will map recursively the primitive object fields into the corresponding cache nodes.

The mapping rule is as follows:

- Create a tree node using `fqn`, if not yet existed.

- Go through all the fields (say, with an association `java.lang.reflect.Field` type field) in POJO,

  - If it is a primitive type, the field value will be stored under `fqn` with `(key, value)` pair of `(field.getName(), field.getValue())`. The following are primitive types supported now: `String, Boolean, Double, Float, Integer, Long, Short, Character, Boolean`.

  - If it is a non-primitive type, creates a child `FQN` and then recursively executes another `pubObject` until it reaches all primitive types.

Following is a code snippet that illustrates this mapping process

             for (Iterator i = type.getFields().iterator(); i.hasNext();) {
                Field field = (Field) i.next();
                Object value = field.get(obj);
                CachedType fieldType = getCachedType(field.getType());
                if (fieldType.isImmediate()) {
                   immediates.put(field.getName(), value);
                } else {
                   putObject(new Fqn(fqn, field.getName()), value);
                }
             }

Let's take an example POJO classes definition from Example POJO section below where we have a `Person` object that has composite non-primitive types (e.g., Set and Address). After we execute the `pubObject` call, the resulting tree node will schematically look like the cache node in the following figures after we have executed calls:

    Person joe = new Person();
    joe.setAddress(new Address());

    cache.putObject("/aop/joe", joe);

The TreeCacheAop APIs will be explained in fuller details later. But notice the illustration of object mapping by reachability in the following figure.

Figure 3. Object mapping by reachability

Under the fqn `"/aop/joe"`, there are three children nodes: `addr`, `skill`, and `language`. If you look at the `Person` class declaration, you will find that `addr` is an `Address` class, `skill` is a `Set`, and `language` is a `List` type. Since they are non-primitive, they are recursively inserted under the parent object (`joe`) until all primitive types are reached. In this way, we have broken down the object graph into a tree view which fit into our internal structure nicely. Also note that all the primitive types will be stored as a `Map` inside the respective node (e.g., `addr` will have `Zip`, `Street`, etc. store there).

Here is a code snippet to demonstrate the object mapping by reachability feature. Notice how a `Person` object (e.g., `joe`) that has complex object references will be mapped into the underlying cache store as explained above.

    import org.jboss.cache.PropertyConfigurator;
    import org.jboss.cache.aop.TreeCacheAop;
    import org.jboss.test.cache.test.standAloneAop.Person;
    import org.jboss.test.cache.test.standAloneAop.Address;

    TreeCacheAop tree = new TreeCacheAop();
    PropertyConfigurator config = new PropertyConfigurator(); // configure tree cache.
    config.configure(tree, "META-INF/replSync-service.xml");

    Person joe = new Person(); // instantiate a Person object named joe
    joe.setName("Joe Black");
    joe.setAge(31);

    Address addr = new Address(); // instantiate a Address object named addr
    addr.setCity("Sunnyvale");
    addr.setStreet("123 Albert Ave");
    addr.setZip(94086);
    joe.setAddress(addr); // set the address reference

    tree.startService(); // kick start tree cache
    tree.putObject("/aop/joe", joe); // add aop sanctioned object (and sub-objects) into cache.
    // since it is advisable, use of plain get/set methods will take care of cache contents automatically.
    joe.setAge(41);

Note that a typical `TreeCacheAop` usage involves instantiating the `TreeCacheAop`, configuration, and start the cache instance. Then, a user creates the advisable POJO that will be put into the cache using `putObject()` API.

In addition, TreeCacheAop also supports get/set with parameter type of some `Collection` classes (i.e., `List`, `Map`, and `Set`). For example, the following code snippet in addition to the above example will trigger TreeCacheAop to manage the states for the `Lanugages` list as well:

    ArrayList lang = new ArrayList();
    lang.add("Ensligh");
    lang.add("Mandarin");
    joe.setLanguages(lang);

### 3.3. Object relationship management

During the mapping process, we will also need to check whether any of its associated object is multiple or circular referenced. A reference counting mechanism has been implemented associating with the `CacheInterceptor`. If the cache detects an object has been referenced more than twice for the first time, it will re-locate the current object node to an internal area. Afterwards, all object node will be referenced indirectly to there.

To look at one example, we say that multiple `Person` objects can own the same `Address` (e.g., a household). Graphically, here is what it will look like in the tree nodes:

Figure 4. Schematic illustration of object graph handling

Notice in the `addr` node for both `joe` and `mary`, they are grayed out and have dashed arrows pointing toward an internal `addr` node instead.

In the following code snippet, we show how a TreeCacheAop can handle multiple object references, namely, an `Address` can be shared among multiple `Person` objects (e.g., `joe` and `mary`).

    import org.jboss.cache.PropertyConfigurator;
    import org.jboss.cache.aop.TreeCacheAop;
    import org.jboss.test.cache.test.standAloneAop.Person;
    import org.jboss.test.cache.test.standAloneAop.Address;

    TreeCacheAop tree = new TreeCacheAop();
    PropertyConfigurator config = new PropertyConfigurator(); // configure tree cache.
    config.configure(tree, "META-INF/replSync-service.xml");

    Person joe = new Person(); // instantiate a Person object named joe
    joe.setName("Joe Black");
    joe.setAge(31);

    Person mary = new Person(); // instantiate a Person object named mary
    mary.setName("Mary White");
    mary.setAge(30);

    Address addr = new Address(); // instantiate a Address object named addr
    addr.setCity("Sunnyvale");
    addr.setStreet("123 Albert Ave");
    addr.setZip(94086);

    joe.setAddress(addr); // set the address reference
    mary.setAddress(addr); // set the address reference

    tree.startService(); // kick start tree
    tree.putObject("/aop/joe", joe); // add aop sanctioned object (and sub-objects) into cache.
    tree.putObject("/aop/mary", mary); // add aop sanctioned object (and sub-objects) into cache.

    Address joeAddr = joe.getAddress();
    Address maryAddr = mary.getAddress(); // joeAddr and maryAddr should be the same

    tree.removeObject("/aop/joe");
    maryAddr = mary.getAddress(); // Should still have the address.

Notice that after we remove `joe` instance from the cache, `mary` should still have reference the same `Address` object in the cache store.

### 3.4. Object inheritance hierarchy

TreeCacheAop preserves the POJO object inheritance hierarchy automatically. For example, if a `Student` extends `Person` with an additional field `year` (see following POJO example), then once `Student` is put into the cache, all the base class attributes of `Person` will be managed as well.

Following is a code snippet that illustrates how the inheritance behavior of a POJO is maintained.

    import org.jboss.test.cache.test.standAloneAop.Student;

    Student joe = new Student();  // Student extends Person class
    joe.setName("Joe Black"); // This is base class attributes
    joe.setAge(22);  // This is also base class attributes
    joe.setYear("Senior"); // This is Student class attribute

    tree.putObject("/aop/student/joe", joe);

    //...

    joe = (Student)tree.putObject("/aop/student/joe");
    Person person = (Person)joe; // it will be correct here
    joe.setYear("Junior"); // will be intercepted by the cache
    joe.setName("Joe Black II"); // also intercepted by the cache

## 4. Requirement

`TreeCacheAop` requires the following libraries (in addition to jboss-cache.jar and the required libraries for the plain TreeCache), and specific class loader (if it is not per-compiled) during start up:

- Library: jboss-aop.jar, trove.jar, and javassist.jar.

- Classloader: To run under the JBoss standalone Aop framework without pre-compiling, you will need to use the AOP system class loader, i.e., you will need to specify the class loader during start up as: -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader.

## 5. API

There are 3 core APIs for `TreeCacheAop`:

- `Object putObject(String fqn, Object pojo)` where `fqn` is a user-specified fully qualified name (`FQN`) to store the node in the underlying cache, e.g., "`/aop/joe`", and `pojo` is the object instance to be managed by `TreeCacheAop`.

  This call returns the existing object under fqn. As a result, a successful call will replace that old value with pojo instance, if it exists. Note that a user will only need to issue this call once for each pojo. Once it is executed, `TreeCacheAop` will assign an interceptor for the pojo instance and its sub-objects.

- `Object getObject(String fqn)`. This call will return the current object content located under `fqn`. This method call is useful when you start a replicated node and you want to get the object reference.

- `Object removeObject(String fqn)`. This call will remove the contents under fqn and return the POJO instance stored there (null if it doesn't exist). Note this call will also remove everything stored under `fqn`.

## 6. Configuration

In addition to the TreeCache configuration xml file, you will also need a `META-INF/jboss-aop.xml` file located under the class path (unless you use aopc to pre-compile the byte code). JBoss AOP framework will read this file during startup to make necessary byte code manipulation for advice and introduction. You will need to declare any of your POJO to be "advisable" so that AOP framework knows to start intercepting either method, field, or constructor invocations. The standalone `TreeCacheAop` package provides an example declaration for the tutorial classes, namely, `Person` and `Address`. Detailed class declaration for `Person` and `Address` are provided in the next section. But here is the snippet for `META-INF/jboss-aop.xml`:

    <aop>
      <prepare expr="field(* $instanceof{org.jboss.test.cache.test.standAloneAop.Address}->*)" />
      <prepare expr="field(* $instanceof{org.jboss.test.cache.test.standAloneAop.Person}->*)" />
    </aop>

Detailed semantics of `jboss-aop.xml` can be found in JBossAop. But above statements basically declare all field read and write will be "advised".

## 7. Example POJO

The example POJO classes used for Tutorial are: `Person` and `Address`. Here is the snippet of the class definition for `Person` and `Address` (note that neither class implements `Serializable`).

    public class Person {
       String name=null;
       int age=0;
       Map hobbies=null;
       Address address=null;
       Set skills;
       List languages;

       public String getName() { return name; }
       public void setName(String name) { this.name=name; }

       public int getAge() { return age; }
       public void setAge(int age) { this.age = age; }

       public Map getHobbies() { return hobbies; }
       public void setHobbies(Map hobbies) { this.hobbies = hobbies; }

       public Address getAddress() { return address; }
       public void setAddress(Address address) { this.address = address; }

       public Set getSkills() { return skills; }
       public void setSkills(Set skills) { this.skills = skills; }

       public List getLanguages() { return languages; }
       public void setLanguages(List languages) { this.languages = languages; }
    }

    public class Student extends Person {
       String year=null;

       public String getYear() { return year; }
       public void setYear(String year) { this.year=year; }
    }

    public class Address {
       String street=null;
       String city=null;
       int zip=0;

       public String getStreet() { return street; }
       public void setStreet(String street) { this.street=street; }
       ...
    }

## 8. TreeCacheAop eviction policy

TreeCacheAop also provides an eviction policy, `org.jboss.cache.eviction.AopLRUPolicy`, that is a subclass of `org.jboss.cache.eviction.LRUPolicy` with the same configuration parameters. Eviction in TreeCacheAop is quite different in that in aop world, first of all the concept of a unit is object (which can have multiple nodes and children nodes).

Second of all, once a user obtains a POJO reference, everything is supposed to be transparent, e.g., cache retrieval and update operations. But if an object is evicted, that means there is no `CacheInterceptor` for the POJO, and the contents are not intercepted by the cache. Instead, everything will be channeled to the in-memory reference. Then a user has no way of knowing this fact!

We could have thrown a runtime exception when a user is accessing a "evicted" node. But this is intrusive and not ideal. What we should do then is to evict an object (by removing all the nodes and children nodes). But we leave the `CacheInterceptor` for that POJO (and any sub-POJOs) alone. This way, when a user is using the POJO methods, it will still get intercepted by the `CacheInterceptor`. And if it finds that the node is empty, then it will also check to see if the eventual invocation from the in-memory reference is null or not. If not null, we know this is an evicted node and we will need to populate this object in TreeCacheAop based on the in-memory value.

## 9. Limitations and Problems

Here are some of the current limitation in `TreeCacheAop`.

- Currently, plain TreeCache and `TreeCacheAop` can share the same fqn name, i.e., the same node. However, if you do remove the node, you will remove any contents associated with that nodes, say, both remove(String fqn) and removeObject(String fqn, Object pojo) method calls. Both method calls currently do not differentiate the content type. This limit will be removed in the future release.