<?xml version="1.0" encoding="UTF-8"?>
<article lang="en">
  <articleinfo>
    <title>JBoss TreeCache/TreeCacheAop Tutorial</title>

    <author>
       Ben Wang 

      <ulink url="mailto:ben.wang@jboss.com">ben.wang@jboss.com</ulink>

       
    </author>

    <author>
       Blea Ban 

      <ulink url="mailto:bela@jboss.com">bela@jboss.com</ulink>

       
    </author>

    <date>Aug 30, 2004</date>
  </articleinfo>

  <section>
    <title>Introduction</title>

    <para>JBoss TreeCache (and TreeCacheAop) is a replicated (synchronous or
    asynchronous) and transactional cache with aspect-oriented programming
    (AOP) capability. In this tutorial, we will demonstrate the usage of the
    cache with and without AOP. For details of the usage and APIs, please
    refer to the user manuals for <ulink
    url="TreeCache.html">TreeCache</ulink> and <ulink
    url="TreeCacheAop.html">TreeCacheAop</ulink>.</para>
  </section>

  <section>
    <title>Scope</title>

    <itemizedlist>
      <listitem>
        <para>Cache creation and modification</para>
      </listitem>

      <listitem>
        <para>Replication</para>
      </listitem>

      <listitem>
        <para>AOP cache</para>
      </listitem>

      <listitem>
        <para>Transaction</para>
      </listitem>
    </itemizedlist>
  </section>

  <section>
    <title>Configuration</title>

    <para>First download the standalone TreeCache code from <ulink
    url="http://sourceforge.net/project/showfiles.php?group_id=22866&amp;release_id=181367">here</ulink>.
    Unzip it, and you will get a root directory (jboss-cache in our example).
    </para>

    <para>The configuration files are located under the etc directory. You can
    modify the behavior of TreeCache through editing the various configuration
    files.</para>

    <itemizedlist>
      <listitem>
        <para>log4j.xml. Logging output. You can turn on logging level or
        change log file directory (default is
        <literal>/tmp/test.log</literal>).</para>
      </listitem>

      <listitem>
        <para>replSync-service.xml. Tree cache configuration file (file name
        is not fixed. You specify the file to be read in in
        <literal>PropertyConfigurator</literal>). The settings are for a
        replicated, synchronous, and transactional cache. The default
        DummyTransactionManager is used with a transaction isolation level of
        REPEATABLE_READ. For details of the configuration parameters, please
        refer to the <ulink url="TreeCache.html">Treecache</ulink>. Note that
        this file is used in the BSH (<ulink
        url="http://www.beanshell.org/">BeanShell</ulink>, a lightweight Java
        compatible scripting language) script to configure the cache.</para>
      </listitem>

      <listitem>
        <para>jboss-aop.xml. AOP pointcut and advice definition for the
        example POJO classes, Person and Address, respectively. For details of
        how to put your own class under AOP, please refer to the <ulink
        url="TreeCacheAop.html">TreeCacheAop</ulink>. This file is read in
        when the process is started.</para>
      </listitem>
    </itemizedlist>
  </section>

  <section>
    <title>Script</title>

    <para>The script files that are needed (located under install directory)
    in this tutorial are:</para>

    <itemizedlist>
      <listitem>
        <para>build.sh (or build.bat for DOS). Simple build script that wraps
        around ant. Users can simply type <literal>sh build.sh</literal> for
        help. Note from now on, we will only refer to the Unix version with
        the understanding that there is a corresponding DOS counterpart. The
        same goes for runDemoShell explained next.</para>
      </listitem>

      <listitem>
        <para>runDemoShell.sh. Simple run script that wraps around BeanShell.
        This is used to operate the replicated cache through interactive
        command line.</para>
      </listitem>

      <listitem>
        <para>plain.bsh. Java codes that instantiate and configure the cache.
        It also creates an example cache entry.</para>
      </listitem>

      <listitem>
        <para>aop.bsh. Java codes that instantiate and configure the aop
        cache. In addition, it also sets up the example POJO (plaing old Java
        object) classes (e.g., Person and Address).</para>
      </listitem>

      <listitem>
        <para>aopWithTx.bsh. Same with aop.bsh except it also instantiates a
        transaction context.</para>
      </listitem>
    </itemizedlist>
  </section>

  <section>
    <title>Example POJO</title>

    <para>The example POJO classes used for TreeCacheAop demo are:
    <literal>Person</literal> and <literal>Address</literal>. They are located
    under <literal>examples/org/jboss/test/cache/test/standAloneAop</literal>
    directory. <literal>Person</literal> has attributes of <literal>String
    age, Address addr, List languages</literal>, etc. We will demonstrate that
    once you put the POJO instance in the cache, plain get/set POJO methods
    will be intercepted by the cache.</para>

    <para>Here is the snippet of the class definition for
    <literal>Person</literal> and <literal>Address</literal>.</para>

    <programlisting>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; } 
   ... 
}</programlisting>

    <programlisting>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; } 
   ... 
}</programlisting>
  </section>

  <section>
    <title>Demo</title>

    <para>To run the demo, you will need at least two windows: one to peruse
    the cache contents (plus non-aop operations) and the other to operate the
    cache directly. Of course, you can also open more than one GUI window to
    see the cache replication at work to multiple members. You will also need
    to run the scripts under jboss-cache installation directory after you
    unzip the release package (jboss-cache-dist.zip). Also note that for each
    demo examples, it'd be best if you re-start the whole setup.</para>

    <para>The two demo programs to run are:</para>

    <itemizedlist>
      <listitem>
        <para>On the first window for the GUI, type <literal>sh
        build.sh</literal> to see the available commands. To run the GUI, type
        <literal>sh build.sh run.aop.demo</literal>. It will startup a
        TreeCacheAop GUI. Later on, you can click on a node to view the the
        contents. Note that you can also add/modify the node contents for
        non-AOP cache entries. Since the GUI entry only accepts String for
        now, operation on aop cache from the GUI will not always work (unless
        it is a String type).</para>
      </listitem>

      <listitem>
        <para>On the second window for the interactive Java commands, type sh
        runShellDemo.sh to fire off the BeanShell interactive command shell
        (you can use either ^D or ^Z in Windows and Unix to exit afterward).
        You can then read in the Java code scripts to showcase the cache
        capabilities (e.g., plain.bsh, aop.bsh, and aopWithTx.bsh). See the
        following for details.</para>
      </listitem>
    </itemizedlist>
  </section>

  <section>
    <title>Plain cache</title>

    <para>Once you are in the shell, you can either execute the script to
    populate the cache, or type it in manually by command line. To run the
    script, type <literal>sourceRelative("plain.bsh");</literal> under the
    interactive BSH shell. For this to work, you'll need to have your working
    directory set to the directory in which plain.bsh resides (otherwise give
    the full pathname of plain.bsh). Basically, the script will create cache
    entries that will be replicated onto the GUI. Here are the snippets for
    the script:</para>

    <programlisting>import org.jboss.cache.*;
show(); // verbose mode for bean shell
TreeCache tree = new TreeCache(); 
PropertyConfigurator config = new PropertyConfigurator(); // configure tree cache. Needs to be in the classpath 
config.configure(tree, "META-INF/replSync-service.xml"); 
tree.startService(); // kick start tree cache
tree.put("/a/b/c", "ben", "me"); // create a cache entry. 
// Node "/a/b/c" will be created if not yet existed.</programlisting>

    <para>You should see in the GUI that a new entry of
    <literal>/a/b/c</literal> has been created. Click on the node c to see the
    content. You can modify the contents from the GUI as well. To create
    another node, for example, you can type in the shell:</para>

    <programlisting>tree.put("/a/b/c/d", "JBoss", "Open Source");
tree.get("/a/b/c/d", "JBoss");</programlisting>
  </section>

  <section>
    <title>CacheAop</title>

    <para>Once you are in the shell, type
    <literal>sourceRelative("aop.bsh");</literal> to execute the shell script.
    Basically, aop.bsh illustrates the steps to instantiate a cache, configure
    it, and then create entries under it. Here are the snippets:</para>

    <programlisting>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; 
show(); // verbose mode for bean shell
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 aop-sanctioned, use of plain get/set methods will take care of cache contents automatically. 
joe.setAge(41);</programlisting>

    <para>Note the API needed to put the object (and its dependent ones) into
    cache is putObject. Once the second window finishes execution, you should
    see the first GUI window has been populated with entries of
    /aop/joe/address. Click on each tree node will display different values
    associated with that node.</para>

    <para>Next step to see AOP in action, you can do plain get/set methods
    without ever worrying about put it in the cache. For example, you can do
    in the shell window joe.setAge(20); and see that GUI gets updated with the
    age field automatically. Also to demonstrate the object graph replication,
    you can modify Joe's address and see the cache will update it
    automatically. For example, type addr.setCity("San Jose"); in the
    interactive shell, you should see in the GUI that the address got
    modified.</para>

    <para>Finally, TreeCacheAop also supports get/set with parameter type of
    Collection classes (i.e., List, Map, and Set). For example, type the
    following in the shell command line:</para>

    <programlisting>ArrayList lang = new ArrayList(); 
lang.add("Ensligh");
lang.add("Mandarin"); 
joe.setLanguages(lang);</programlisting>
  </section>

  <section>
    <title>CacheAop with Transaction</title>

    <para>To see TreeCache transaction at work, you start with the same setup
    with last section except you load the bsh of aopWithTx.bsh instead of
    aop.bsh. The additional snippets are:</para>

    <programlisting>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;// Tx imports 
import javax.transaction.UserTransaction; import javax.naming.*; 
import org.jboss.cache.transaction.DummyTransactionManager; 
show(); // verbose mode for bean shell
// Set up transaction manager 
DummyTransactionManager.getInstance(); 
Properties prop = new Properties(); 
prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
UserTransaction tx = (UserTransaction)new InitialContext(prop).lookup("UserTransaction");
TreeCacheAop tree = new TreeCacheAop();
PropertyConfigurator config = new PropertyConfigurator(); // configure tree cache.
config.configure(tree, "META-INF/replSync-service.xml"); 
joe = new Person();
joe.setName("Joe Black"); 
joe.setAge(31); 

Address addr = new Address();
addr.setCity("Sunnyvale"); 
addr.setStreet("123 Albert Ave");
addr.setZip(94086); 
joe.setAddress(addr); 

tree.startService(); // kick start tree cache
tree.putObject("/aop/joe", joe); // add aop sanctioned object
// since it is aop-sanctioned, use of plain get/set methods will take care of cache contents automatically. 
// Also it is transacted 
tx.begin(); 
joe.setAge(41);
joe.getAddress().setZip(95124); 
tx.commit();</programlisting>

    <para>In this example, a default dummy transaction manager is used.</para>

    <programlisting>tx.begin(); 
addr.setZip(95131);
tx.rollback();</programlisting>
  </section>

  <section>
    <title>CacheLoader examples</title>

    <para>All the examples below are based on the JBossCache standalone
    distribution. We assume the ZIP file has been unzipped into a directory
    <literal>jboss-cache</literal>.</para>

    <section>
      <title>Local cache with CacheLoader</title>

      <para>This demo shows a local TreeCacheAop with a CacheLoader. We will
      insert a POJO into the cache, and see that the POJO is transparently
      saved using the CacheLoader.</para>

      <para>To run this, you have to modify
      <literal>jboss-cache/output/etc/META-INF/oodb-service.xml</literal>:
      change <literal>CacheLoaderConfig</literal> to point to a valid
      directory (create it if it doesn't yet exist):</para>

      <programlisting>&lt;attribute name="CacheLoaderConfig"&gt;c:\tmp\oodb&lt;/attribute&gt;
</programlisting>

      <para>Then start the beanshell and source <literal>oodb.bsh</literal>
      into it:</para>

      <programlisting>bela@laptop /cygdrive/c/jboss-cache
$ ./runShellDemo.sh
BeanShell 1.3.0 - by Pat Niemeyer (pat@pat.net)
bsh % sourceRelative("oodb.bsh");
interceptor chain is:
class org.jboss.cache.interceptors.CallInterceptor
class org.jboss.cache.interceptors.CacheLoaderInterceptor
class org.jboss.cache.interceptors.TransactionInterceptor
&lt;null&gt;
bsh %</programlisting>

      <para>Next, create an instance of Person, and set its address and other
      fields:</para>

      <programlisting>bsh % p=new Person();
&lt;name=null, age=0, hobbies=, address=null, skills=null, languages=null&gt;
bsh % p.age=3;
&lt;3&gt;
bsh % p.name="Michelle";
&lt;Michelle&gt;
bsh % addr=new Address();
&lt;street=null, city=null, zip=0&gt;
bsh % addr.city="San Jose";
&lt;San Jose&gt;
bsh % addr.zip=95124;
&lt;95124&gt;
bsh % addr.street="1704 Almond Blossom Lane";
&lt;1704 Almond Blossom Lane&gt;
bsh % p.setAddress(addr);
bsh % p;
&lt;name=Michelle, age=3, hobbies=, address=street=1704 Almond Blossom Lane, city=San Jose, zip=95124, skills=null, languages=null&gt;
bsh %</programlisting>

      <para>The <literal>Person</literal> object with all of its fields and
      subobjects is now saved. Let's kill beanshell and restart it. At this
      point, because the instance of <literal>Person</literal> we created was
      given the name "p", we can retrieve it again:</para>

      <programlisting>bela@laptop /cygdrive/c/jboss-cache
$ ./runShellDemo.sh
BeanShell 1.3.0 - by Pat Niemeyer (pat@pat.net)
bsh % sourceRelative("oodb.bsh");
interceptor chain is:
class org.jboss.cache.interceptors.CallInterceptor
class org.jboss.cache.interceptors.CacheLoaderInterceptor
class org.jboss.cache.interceptors.TransactionInterceptor
&lt;null&gt;
bsh % tree;
&lt;/&gt;
bsh % p=tree.getObject("p");
&lt;name=Michelle, age=3, hobbies=, address=street=1704 Almond Blossom Lane, city=San Jose, zip=95124, skills=null, languages=null&gt;
bsh % tree;
&lt;/p
    /address
&gt;
bsh %</programlisting>

      <para>The interesting thing here is that the cache was initially empty
      ("/"). Only when we loaded "p", did it get populated (lazy loading). You
      can see that the values of "p" are loaded from the datastore where they
      were previously saved.</para>

      <para></para>
    </section>

    <section>
      <title>Replicated cache with shared datastore</title>

      <para>The scenario that we'll run in this example is described in the
      documentation for JBossCache. It consists of 2 separate nodes that
      replicate their contents between each other. In addition, they both
      point to the <emphasis>same</emphasis> datastore. The configuration is
      in file
      <literal>jboss-cache/output/etc/META-INF/replAsyncSharedCacheLoader-service.xml</literal>:</para>

      <programlisting>&lt;!-- Whether or not to fetch state on joining a cluster --&gt;
&lt;attribute name="FetchStateOnStartup"&gt;false&lt;/attribute&gt;
&lt;attribute name="CacheLoaderClass"&gt;org.jboss.cache.loader.FileCacheLoader&lt;/attribute&gt;
&lt;attribute name="CacheLoaderConfig"&gt;c:\tmp&lt;/attribute&gt;
&lt;attribute name="CacheLoaderShared"&gt;true&lt;/attribute&gt;
&lt;attribute name="CacheLoaderPreload"&gt;/&lt;/attribute&gt;
&lt;attribute name="CacheLoaderFetchTransientState"&gt;false&lt;/attribute&gt;
&lt;attribute name="CacheLoaderFetchPersistentState"&gt;true&lt;/attribute&gt;</programlisting>

      <para>The <literal>FetchStateOnStartup</literal> attribute set to
      <literal>false</literal> means that a newly started cache will
      <emphasis>not</emphasis> attempt to fetch the state (neither transient
      nor persistent). Therefore, attributes
      <literal>CacheLoaderFetchTransientState</literal> and
      <literal>CacheLoaderFetchPersistentState</literal> will be ignored.
      <literal>CacheLoaderShared</literal> set to <literal>true</literal>
      means that both nodes will share the same datastore, which resides in
      <literal>c:\tmp</literal> in the example (this assumes that both nodes
      have access to the same file system). Please make sure that
      <literal>c:\tmp</literal> exists, or point the config string to a
      different existing directory.</para>

      <para>This configuration would essentially provide for two
      <literal>cold</literal> nodes, in the sense that all contents of a new
      cache is in the datastore, and is lazy-loaded via the CacheLoader when
      accessed. However, this is not true, as
      <literal>CacheLoaderPreload</literal> points to "/", which is the root
      of the entire tree. Therefore, all of the contents of the cache are
      recursively pre-loaded. This is probably a bad configuration when you
      have a lot of data in the cache, because <emphasis>all</emphasis> of
      your data will be loaded into the cache.</para>

      <para>Note that with a shared datastore, the node that makes a
      modification is the one who writes it to the store using the
      CacheLoader. This prevents both nodes from writing the same data
      twice.</para>

      <para>We can now start 2 instances by opening two shells and executing
      the following ANT target:</para>

      <programlisting>bela@laptop /cygdrive/c/jboss-cache
$ ./build.sh run.demo.async.shared.cacheloader
Buildfile: build.xml

init:

compile:

run.demo.async.shared.cacheloader:
     [java] ** node loaded: /a
     [java] ** node loaded: /a/b
     [java] ** node loaded: /a/b/c
     [java] ** node loaded: /uno
     [java] ** node loaded: /uno/due

     [java] -------------------------------------------------------
     [java] GMS: address is 192.168.1.184:1357
     [java] -------------------------------------------------------
     [java] interceptor chain is:
     [java] class org.jboss.cache.interceptors.CallInterceptor
     [java] class org.jboss.cache.interceptors.ReplicationInterceptor
     [java] class org.jboss.cache.interceptors.CacheLoaderInterceptor
     [java] class org.jboss.cache.interceptors.TransactionInterceptor
     [java] ** view change: [192.168.1.184:1355|1] [192.168.1.184:1355, 192.168.1.184:1357]
     [java] ** node modified: /
</programlisting>

      <para>2 GUI instances will appear, showing the tree structure of the
      cache graphically. Nodes can be added, modified or removed by
      right-clicking or using the menu. Any modification is replicated between
      the two nodes. If both nodes are killed, and subsequently one or both
      nodes are restarted, the state is the same as before shutdown as it was
      persisted to the shared store via the CacheLoader.</para>

      <para>Note that the example above shows the 2 nodes running on the same
      machine (192.168.1.184) on ports 1355 and 1357.</para>
    </section>

    <section>
      <title>Replicated cache with unshared (local) datastore</title>

      <para>In this example, we'll run 2 nodes again, but this time, instead
      of sharing the same datastore, each node has its own datastore. The
      configuration is in file
      <literal>jboss-cache/output/etc/META-INF/node{1,2}.xml</literal>. We'll
      look at node1.xml:</para>

      <programlisting>&lt;attribute name="CacheLoaderClass"&gt;org.jboss.cache.loader.bdbje.BdbjeCacheLoader&lt;/attribute&gt;
&lt;attribute name="CacheLoaderConfig"&gt;c:\tmp\node1&lt;/attribute&gt;
&lt;attribute name="CacheLoaderShared"&gt;false&lt;/attribute&gt;
&lt;attribute name="CacheLoaderPreload"&gt;/&lt;/attribute&gt;
&lt;attribute name="CacheLoaderFetchTransientState"&gt;false&lt;/attribute&gt;
&lt;attribute name="CacheLoaderFetchPersistentState"&gt;true&lt;/attribute&gt;
</programlisting>

      <para>Again, we use the Sleepycat CacheLoader implementation in
      <literal>CacheLoaderClass</literal>. The
      <literal>CacheLoaderConfig</literal> points to
      <literal>c:\tmp\node1</literal>. This is the directory in which the
      Sleepycat DB for node1 will reside. File node2.xml has a configuration
      that points to <literal>c:\tmp\node2</literal>, so we have 2 different
      unshared datastores. Note that, of course, we still have the same
      filesystem in our case, because we run the 2 nodes on the same machine.
      In practice those two directories would reside on two different
      machines, and each machine would run one JBossCache process. Note that
      the 2 directories have to exis</para>

      <para>To create an unshared datastore, we set the
      <literal>CacheLoaderShared</literal> attribute to
      <literal>false</literal>.</para>

      <para>The example can be run by again opening 2 shells, and running 2
      ANT targets (here we show the target for node1):</para>

      <programlisting>bela@laptop /cygdrive/c/jboss-cache
$ ./build.sh run.demo.unshared.node2
Buildfile: build.xml

init:

compile:

run.demo.unshared.node2:
     [java] ** node loaded: /a
     [java] ** node loaded: /a/a2
...</programlisting>

      <para>The <literal>run.demo.unshared.node2</literal> target runs node2,
      which will have its own store located at <literal>c:\tmp\node2</literal>
      (shown above). Whenever a change is made on either of the 2 nodes, it is
      replicated to the other node, and persisted in both local datastores.
      You can kill and restart a node, or even both nodes, and the data will
      still be available due to the persistent backend store(s).</para>
    </section>
  </section>

  <section>
    <title>Troubleshooting</title>

    <para>Here are some tips for troubleshooting, if you encounter problems
    during this demo.</para>

    <itemizedlist>
      <listitem>
        <para>Most of the time, the problem will come from cache replication
        layer, i.e., JGroups package. On the output window, you can see the
        JGroups membership view. See if it is updated when you run the BSH
        commands. It should show a view with at least two members. For
        example, on my window, I see</para>

        <programlisting> [java] ** view change: [BWANG-HOME:4381|1] [BWANG-HOME:4381, BWANG-HOME:4383]</programlisting>

        <para>with 2 members: 4381 and 4383. On the other hand, if you don't
        close the previous running cache instance, the membership view will
        also include the previous existing ones. This can corrupt the states.
        So you will have to make sure there is no running TreeCache processes
        before each demo. If you have problem with this, please consult the
        <ulink
        url="http://www.jgroups.org/javagroupsnew/docs/index.html">JGroups
        website</ulink>.</para>
      </listitem>
    </itemizedlist>
  </section>
</article>