Chapter 4. Architecture
Prev |  | Next

## Chapter 4. Architecture

Following explains the concepts and top-level design of PojoCache.

## 4.1. Dynamic AOP interception

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

JBossAop has the capability to intercept both method level call and field level read write. From the perspective of PojoCache, field level interception is the appropriate mechanism to synchronize with the backend cache store. Please note that,

- the filed level interception applies to all access qualifiers. That is, regardless whether it is `public`, `protected`, or `private`
- we skip interception for field with `final`, `static`, and `transient` qualifiers. As a result, any field with these 3 qualifiers will not be replicated or persisted.

The figures shown below illustrate 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, you should see the difference between these figures. While field write operation will go to cache first and, then, invoke the in-memory update, the field read invocation does not involve in-memory reference at all. This is because the value in cache and memory should have been synchronized during write operation. As a result, the field value from the cache is returned.

Figure 4.1. Dynamic AOP interception for field write

Figure 4.2. Dynamic AOP Interception for field read

## 4.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 "prepared" (e.g., a `Person` object), during the `putObject(Fqn fqn, Object pojo)` operation, PojoCache 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.`

  - 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 class definition from the Appendix section where we have a `Person` object that has composite non-primitive types (e.g., List and Address). After we execute the `putObject` call, the resulting tree node will schematically look like the cache node in the following figures:

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

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

The PojoCache APIs will be explained in fuller details later. But notice the illustration of object mapping by reachability in the following figure. The fqn `/aop/joe` is associated with the POJO `joe`. Then under that fqn, there are three children nodes: `addr,` `skills,` and `languages.` If you look at the `Person` class declaration, you will find that `addr` is an `Address` class, `skills` is a `Set` , and `languages` 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 inside the respective node's HashMap (e.g., `addr` will have `Zip` , `Street` , etc. stored there).

Figure 4.3. Object mapping by reachability

Here is a code snippet to demonstrate the object mapping by reachability feature that we just explained. 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.PojoCache;
    import org.jboss.test.cache.test.standAloneAop.Person;
    import org.jboss.test.cache.test.standAloneAop.Address;

    PojoCache tree = new PojoCache();
    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 aspectized, use of plain get/set methods will take care of cache contents automatically.
    joe.setAge(41);

Note that a typical `PojoCache` usage involves instantiating the `PojoCache` , configuring, and starting the cache instance. Then, a user creates the aspectized POJO that will be put into the cache using `putObject()` API.

In addition, PojoCache also supports get/set with parameter type of some `Collection` classes (i.e., `List` , `Map` , and `Set` ) automatically. For example, the following code snippet in addition to the above example will trigger PojoCache to manage the states for the `Languages` list as well. The following figure illustrates the node structure from the replicated GUI point of view. Details of Collection class support will be given later.

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

Figure 4.4. Schematic illustration of List class mapping

## 4.3. Object relationship management

Like we have mentioned, traditional cache system does not support object relationship management during serialization (be it to the persistent data store or replicated to the other in-memory nodes.) Examples of object relationship are like an address object is shared by members of the household, and a child-parent relationship. All these relationship will be lost once the objects are replicated or persisted. As a result, explicit mapping will be needed outside of the cache system to express the object relationship. PojoCache, in contrast, can manage object relationship transparently for users.

During the mapping process, we will check whether any of its associated object is multiple or circular referenced. A reference counting mechanism has been implemented associating with the `CacheInterceptor`. If a new object created in the cache referenced to another POJO, a referenced `fqn` will be stored there to redirect any query and update to the original node.

To look at one example, let's say that multiple `Person`s ("joe" and "mary") objects can own the same `Address` (e.g., a household). Graphically, here is what it will look like in the tree nodes. Like we have covered in the previous section on the mapping by reachability, the POJO will map recursively into the cache. However, when we detect a multiple reference (in this case, the `Address`), we will keep track of the reference counting for the sub-object `addr.`

Figure 4.5. Schematic illustration of object relationship mapping

In the following code snippet, we show programmatically the object sharing example.

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

    PojoCache tree = new PojoCache();
    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 instance

    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.

To further illustrate this relationship management, let's examine the Java code under a replicated environment. Imagine that we have two separate cache instances in the cluster now (`cache1` and `cache2`). Let's say, on the first cache instance, we put both `joe` and `mary` under cache management as above. Then, we failover to `cache2.` Here is the code snippet:

    /**
     * Code snippet on cache2 during fail-over
     */
    import org.jboss.cache.PropertyConfigurator;
    import org.jboss.cache.aop.PojoCache;
    import org.jboss.test.cache.test.standAloneAop.Person;
    import org.jboss.test.cache.test.standAloneAop.Address;

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

    tree.startService(); // kick start tree
    Person joe = tree.getObject("/aop/joe"); // retrieve the POJO reference.
    Person mary = tree.getObject("/aop/mary"); // retrieve the POJO reference.

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

    maryAddr = mary.getAddress().setZip(95123);
    int zip = joeAddr.getAddress().getZip(); // Should be 95123 as well instead of 94086!

## 4.4. Object inheritance hierarchy

PojoCache preserves the POJO object inheritance hierarchy automatically. For example, if a `Student` extends `Person` with an additional field `year` (see POJO definition in the Appendix section), then once `Student` is put into the cache, all the class attributes of `Person` can be managed as well.

Following is a code snippet that illustrates how the inheritance behavior of a POJO is maintained. Again, no special configuration is needed.

    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.5. Collection class proxy

The POJO classes that inherit from `Set` , `List` , and `Map` are treated as "aspectized" automatically. That is, users need not declare them "prepared" in the xml configuration file or via annotation. Since we are not allowed to instrument the Java system library, we will use a proxy approach instead. That is, when we encounter any Collection instance, we will:

- Create a Collection proxy instance and place it in the cache (instead of the original reference). The mapping of the Collection elements will still be carried out recursively as expected.
- If the Collection instance is a sub-object, e.g., inside another POJO, we will swap out the original reference with the new proxy one to promote transparent usage.

To obtain the proxy reference, users can then use another `getObject` to retrieve this proxy reference and use this reference to perform POJO operations.

Here is a code snippet that illustrates the usage of a Collection proxy reference:

    List list = new ArrayList();
    list.add("ONE");
    list.add("TWO");

    tree.putObject("/aop/list", list);
    list.add("THREE"); // This won't intercept by the cache!

    List proxyList = tree.getObject("/aop/list"; // Note that list is a proxy reference
    proxyList.add("FOUR"); // This will be intercepted by the cache

Here is another snippet to illustrate the dynamic swapping of the Collection reference when it is embedded inside another object:

    Person joe = new Person();
    joe.setName("Joe Black"); // This is base class attributes
    ArrayList lang = new ArrayList();
    lang.add("English");
    lang.add("Mandarin");
    joe.setLanguages(lang);
    // This will map the languages List automatically and swap it out with the proxy reference.
    tree.putObject("/aop/student/joe", joe);
    ArrayList lang = joe.getLanguages(); // Note that lang is a proxy reference
    lang.add("French"); // This will be intercepted by the cache

As you can see, `getLanguages` simply returns the field reference that has been swapped out for the proxy reference counterpart.

Finally, when you remove a Collection reference from the cache (e.g., via `removeObject`), you still can use the proxy reference since we will update the in-memory copy of that reference during detachment. Below is a code snippet illustrating this:

    List list = new ArrayList();
    list.add("ONE");
    list.add("TWO");

    tree.putObject("/aop/list", list);
    List proxyList = tree.getObject("/aop/list"); // Note that list is a proxy reference
    proxyList.add("THREE"); // This will be intercepted by the cache

    tree.removeObject("/aop/list"); // detach from the cache
    proxyList.add("FOUR"); // proxyList has 4 elements still.

### 4.5.1. Limitation

Use of Collection class in PojoCache helps you to track fine-grained changes in your collection fields automatically. However, current implementation has the follow limitation that we plan to address soon.

Currently, we only support a limited implementation of Collection classes. That is, we support APIs in List, Set, and Map. However, since the APIs do not stipulate of constraints like NULL key or value, it makes mapping of user instance to our proxy tricky. For example, ArrayList would allow NULL value and some other implementation would not. The Set interface maps to java.util.HashSet implementation. The List interface maps to java.util.ArrayList implementation. The Map interface maps to java.util.HashMap implementation.

Another related issue is the expected performance. For example, the current implementation is ordered, so that makes insert/delete from the Collection slow. Performance between Set, Map and List collections also vary. Adding items to a Set is slower than a List or Map, since Set does not allow duplicate entries.

Prev | Up | Next
Chapter 3. Introduction to JBossAop | Home | Chapter 5. API