This chapter will provide you all the basic knowledge about eXo Cache, from basic concepts to advanced concepts, sample codes, and more.

All applications on the top of eXo JCR that need a cache, can rely on an org.exoplatform.services.cache.ExoCache instance that is managed by the org.exoplatform.services.cache.CacheService. The main implementation of this service is org.exoplatform.services.cache.impl.CacheServiceImpl which depends on the org.exoplatform.services.cache.ExoCacheConfig in order to create new ExoCache instances. See the below example of org.exoplatform.services.cache.CacheService definition:


  <component>
    <key>org.exoplatform.services.cache.CacheService</key>
    <jmx-name>cache:type=CacheService</jmx-name>
    <type>org.exoplatform.services.cache.impl.CacheServiceImpl</type>
    <init-params>
      <object-param>
        <name>cache.config.default</name>
        <description>The default cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>default</string></field>
          <field name="maxSize"><int>300</int></field>
          <field name="liveTime"><long>600</long></field>
          <field name="distributed"><boolean>false</boolean></field>
          <field name="implementation"><string>org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache</string></field> 
        </object>
      </object-param>
    </init-params>
  </component>

See the below example about how to define a new ExoCacheConfig thanks to a external-component-plugin:


  <external-component-plugins>
    <target-component>org.exoplatform.services.cache.CacheService</target-component>
    <component-plugin>
      <name>addExoCacheConfig</name>
      <set-method>addExoCacheConfig</set-method>
      <type>org.exoplatform.services.cache.ExoCacheConfigPlugin</type>
      <description>Configures the cache for query service</description>
      <init-params>
        <object-param>
          <name>cache.config.wcm.composer</name>
          <description>The default cache configuration</description>
          <object type="org.exoplatform.services.cache.ExoCacheConfig">
            <field name="name"><string>wcm.composer</string></field>
            <field name="maxSize"><int>300</int></field>
            <field name="liveTime"><long>600</long></field>
            <field name="distributed"><boolean>false</boolean></field>
            <field name="implementation"><string>org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache</string></field> 
          </object>
        </object-param>
      </init-params>
    </component-plugin>
  </external-component-plugins>

In case, you have big values or non serializable values and you need a replicated cache to at list invalidate the data when it is needed, you can use the invalidation mode that will work on top of any replicated cache implementations. This is possible thanks to the class InvalidationExoCache which is actually a decorator whose idea is to replicate the the hash code of the value in order to know if it is needed or not to invalidate the local data, if the new hash code of the value is the same as the old value, we assume that it is the same value so we don't invalidate the old value. This is required to avoid the following infinite loop that we will face with invalidation mode proposed out of the box by JBoss Cache for example:

In the use case above, thanks to the InvalidationExoCache since the value loaded at step #3 has the same hash code as the value loaded as step #1, the step #4 won't invalidate the data on the cluster node #1.

It exists 2 ways to use the invalidation mode which are the following:

If the data that you want to store into your eXo Cache instance takes a lot of time to load and/or you would like to prevent multiple concurrent loading of the same data at the same time, you can use org.exoplatform.services.cache.future.FutureExoCache on top of your eXo Cache instance in order to delegate the loading of your data to a loader that will be called only once whatever the total amount of concurrent threads looking for it. See below an example of how the FutureExoCache can be used:

import org.exoplatform.services.cache.future.Loader;

import org.exoplatform.services.cache.future.FutureExoCache;
...
   // Define first your loader and choose properly your context object in order
   // to be able to reuse the same loader for different FutureExoCache instances
   Loader<String, String, String> loader = new Loader<String, String, String>()
   { 
      public String retrieve(String context, String key) throws Exception
      {
         return "Value loaded thanks to the key = '" + key + "' and the context = '" + context + "'";
      }
   };
   // Create your FutureExoCache from your eXo cache instance and your loader
   FutureExoCache<String, String, String> myFutureExoCache = new FutureExoCache<String, String, String>(loader, myExoCache);
   // Get your data from your future cache instance
   System.out.println(myFutureExoCache.get("my context", "foo"));

In the previous versions of eXo kernel, it was quite complex to implement your own ExoCache because it was not open enough. Since kernel 2.0.8, it is possible to easily integrate your favorite cache provider in eXo Products.

You just need to implement your own ExoCacheFactory and register it in an eXo container, as described below:

package org.exoplatform.services.cache;

...
public interface ExoCacheFactory {
  
  /**
   * Creates a new instance of {@link org.exoplatform.services.cache.ExoCache}
   * @param config the cache to create
   * @return the new instance of {@link org.exoplatform.services.cache.ExoCache}
   * @exception ExoCacheInitException if an exception happens while initializing the cache
   */
  public ExoCache createCache(ExoCacheConfig config) throws ExoCacheInitException;  
}

As you can see, there is only one method to implement which can be seen as a converter of an ExoCacheConfig to get an instance of ExoCache. Once, you created your own implementation, you can simply register your factory by adding a file conf/portal/configuration.xml with a content of the following type:


<configuration>
  <component>
    <key>org.exoplatform.services.cache.ExoCacheFactory</key>
    <type>org.exoplatform.tutorial.MyExoCacheFactoryImpl</type>
    ...
  </component>   
</configuration>

When you add, the eXo library in your classpath, the eXo service container will use the default configuration provided in the library itself but of course you can still redefined the configuration if you wish as you can do with any components.

The default configuration of the factory is:


<configuration>  
  <component>
    <key>org.exoplatform.services.cache.ExoCacheFactory</key>
    <type>org.exoplatform.services.cache.impl.jboss.ExoCacheFactoryImpl</type>
    <init-params>
      <value-param>
        <name>cache.config.template</name>
        <value>jar:/conf/portal/cache-configuration-template.xml</value>
      </value-param>
      <value-param>
        <name>allow.shareable.cache</name>
        <value>true</value>
      </value-param>
    </init-params>
  </component>   
</configuration>

If for a given reason, you need to use a specific configuration for a cache, you can register one thanks to an "external plugin", see an example below:


<configuration>
  ...
  <external-component-plugins>
    <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component>
    <component-plugin>
      <name>addConfig</name>
      <set-method>addConfig</set-method>
      <type>org.exoplatform.services.cache.impl.jboss.ExoCacheFactoryConfigPlugin</type>
      <description>add Custom Configurations</description>
      <init-params>
        <value-param>
          <name>myCustomCache</name>
          <value>jar:/conf/portal/custom-cache-configuration.xml</value>
        </value-param>         
      </init-params>
    </component-plugin>    
  </external-component-plugins> 
  ...   
</configuration>

In the example above, I call the method addConfig(ExoCacheFactoryConfigPlugin plugin) on the current implementation of ExoCacheFactory which is actually the jboss cache implementation.

In the init-params block, you can define a set of value-param blocks and for each value-param, we expect the name of cache that needs a specific configuration as name and the location of your custom configuration as value.

In this example, we indicates to the factory that we would like that the cache myCustomCache use the configuration available at jar:/conf/portal/custom-cache-configuration.xml.

The factory for jboss cache, delegates the cache creation to ExoCacheCreator that is defined as below:

package org.exoplatform.services.cache.impl.jboss;

...
public interface ExoCacheCreator {
  /**
   * Creates an eXo cache according to the given configuration {@link org.exoplatform.services.cache.ExoCacheConfig}
   * @param config the configuration of the cache to apply
   * @param cache the cache to initialize
   * @exception ExoCacheInitException if an exception happens while initializing the cache
   */
  public ExoCache create(ExoCacheConfig config, Cache<Serializable, Object> cache) throws ExoCacheInitException;
  
  /**
   * Returns the type of {@link org.exoplatform.services.cache.ExoCacheConfig} expected by the creator  
   * @return the expected type
   */
  public Class<? extends ExoCacheConfig> getExpectedConfigType();
  
  /**
   * Returns the name of the implementation expected by the creator. This is mainly used to be backward compatible
   * @return the expected by the creator
   */
  public String getExpectedImplementation();
}

The ExoCacheCreator allows you to define any kind of jboss cache instance that you would like to have. It has been designed to give you the ability to have your own type of configuration and to always be backward compatible.

In an ExoCacheCreator, you need to implement 3 methods which are:

You can register any cache creator that you want thanks to an "external plugin", see an example below:


  <external-component-plugins>
    <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component>
    <component-plugin>
      <name>addCreator</name>
      <set-method>addCreator</set-method>
      <type>org.exoplatform.services.cache.impl.jboss.ExoCacheCreatorPlugin</type>
      <description>add Exo Cache Creator</description>
      <init-params>
        <object-param>
          <name>LRU</name>
          <description>The lru cache creator</description>
          <object type="org.exoplatform.services.cache.impl.jboss.lru.LRUExoCacheCreator">
            <field name="defaultTimeToLive"><long>1500</long></field>
            <field name="defaultMaxAge"><long>2000</long></field>
          </object>
        </object-param>        
      </init-params>
    </component-plugin>
  </external-component-plugins>

In the example above, I call the method addCreator(ExoCacheCreatorPlugin plugin) on the current implementation of ExoCacheFactory which is actually the jboss cache implementation.

In the init-params block, you can define a set of object-param blocks and for each object-param, we expect any object definition of type ExoCacheCreator.

In this example, we register the action creator related to the eviction policy LRU.

By default, no cache creator are defined, so you need to define them yourself by adding them in your configuration files.

You have 2 ways to define a cache which are:


...
  <external-component-plugins>
    <target-component>org.exoplatform.services.cache.CacheService</target-component>
    <component-plugin>
      <name>addExoCacheConfig</name>
      <set-method>addExoCacheConfig</set-method>
      <type>org.exoplatform.services.cache.ExoCacheConfigPlugin</type>
      <description>add ExoCache configuration component plugin </description>
      <init-params>
        ...    
        <object-param>      
          <name>fifoCache</name>
          <description>The fifo cache configuration</description>
          <object type="org.exoplatform.services.cache.ExoCacheConfig">
            <field name="name"><string>fifocache</string></field>
            <field name="maxSize"><int>${my-value}</int></field>
            <field name="liveTime"><long>${my-value}</long></field>
            <field name="distributed"><boolean>false</boolean></field>
            <field name="implementation"><string>org.exoplatform.services.cache.FIFOExoCache</string></field>
          </object>
        </object-param>   
...      
      </init-params>
    </component-plugin>
  </external-component-plugins> 
...

In this example, we define a new cache called fifocache which is in fact the same cache as in previous example but defined in a different manner.


...
       <object-param>
        <name>lru</name>
        <description>The lru cache configuration</description>
        <object type="org.exoplatform.services.cache.impl.jboss.lru.LRUExoCacheConfig">
          <field name="name"><string>lru</string></field>
          <field name="maxNodes"><int>${my-value}</int></field>
          <field name="minTimeToLive"><long>${my-value}</long></field>
          <field name="maxAge"><long>${my-value}</long></field>
          <field name="timeToLive"><long>${my-value}</long></field>
        </object>
      </object-param> 
...

  • Old configuration


...
      <object-param>
        <name>lru-with-old-config</name>
        <description>The lru cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>lru-with-old-config</string></field>
          <field name="maxSize"><int>${my-value}</int></field>
          <field name="liveTime"><long>${my-value}</long></field>
          <field name="implementation"><string>LRU</string></field>
        </object>
      </object-param> 
...

Note

For the fields maxAge and timeToLive needed by JBoss cache, we will use the default values provided by the creator.


...
       <object-param>
        <name>fifo</name>
        <description>The fifo cache configuration</description>
        <object type="org.exoplatform.services.cache.impl.jboss.fifo.FIFOExoCacheConfig">
          <field name="name"><string>fifo</string></field>
          <field name="maxNodes"><int>${my-value}</int></field>
          <field name="minTimeToLive"><long>${my-value}</long></field>
        </object>
      </object-param>
...

  • Old configuration


...
       <object-param>
        <name>fifo-with-old-config</name>
        <description>The fifo cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>fifo-with-old-config</string></field>
          <field name="maxSize"><int>${my-value}</int></field>
          <field name="liveTime"><long>${my-value}</long></field>
          <field name="implementation"><string>FIFO</string></field>
        </object>
      </object-param>
...


...
       <object-param>
        <name>mru</name>
        <description>The mru cache configuration</description>
        <object type="org.exoplatform.services.cache.impl.jboss.mru.MRUExoCacheConfig">
          <field name="name"><string>mru</string></field>
          <field name="maxNodes"><int>${my-value}</int></field>
          <field name="minTimeToLive"><long>${my-value}</long></field>
        </object>
      </object-param> 
...

  • Old configuration


...
      <object-param>
        <name>mru-with-old-config</name>
        <description>The mru cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>mru-with-old-config</string></field>
          <field name="maxSize"><int>${my-value}</int></field>
          <field name="liveTime"><long>${my-value}</long></field>
          <field name="implementation"><string>MRU</string></field>
        </object>
      </object-param> 
...


...
       <object-param>
        <name>lfu</name>
        <description>The lfu cache configuration</description>
        <object type="org.exoplatform.services.cache.impl.jboss.lfu.LFUExoCacheConfig">
          <field name="name"><string>lfu</string></field>
          <field name="maxNodes"><int>${my-value}</int></field>
          <field name="minNodes"><int>${my-value}</int></field>
          <field name="minTimeToLive"><long>${my-value}</long></field>
        </object>
      </object-param> 
...

  • Old configuration


...
      <object-param>
        <name>lfu-with-old-config</name>
        <description>The lfu cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>lfu-with-old-config</string></field>
          <field name="maxSize"><int>${my-value}</int></field>
          <field name="liveTime"><long>${my-value}</long></field>
          <field name="implementation"><string>LFU</string></field>
        </object>
      </object-param> 
...

Note

For the fields minNodes and timeToLive needed by JBoss cache, we will use the default values provided by the creator.


...
       <object-param>
        <name>ea</name>
        <description>The ea cache configuration</description>
        <object type="org.exoplatform.services.cache.impl.jboss.ea.EAExoCacheConfig">
          <field name="name"><string>ea</string></field>
          <field name="maxNodes"><int>${my-value}</int></field>
          <field name="minTimeToLive"><long>${my-value}</long></field>
          <field name="expirationTimeout"><long>${my-value}</long></field>
        </object>
      </object-param> 
...

  • Old configuration


...
      <object-param>
        <name>ea-with-old-config</name>
        <description>The ea cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>lfu-with-old-config</string></field>
          <field name="maxSize"><int>${my-value}</int></field>
          <field name="liveTime"><long>${my-value}</long></field>
          <field name="implementation"><string>EA</string></field>
        </object>
      </object-param> 
...

Note

For the fields expirationTimeout needed by JBoss cache, we will use the default values provided by the creator.

If for a given reason, you need to use a specific configuration for a cache, you can register one thanks to an "external plugin", see an example below:


<configuration>
  ...
  <external-component-plugins>
    <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component>
    <component-plugin>
      <name>addConfig</name>
      <set-method>addConfig</set-method>
      <type>org.exoplatform.services.cache.impl.infinispan.ExoCacheFactoryConfigPlugin</type>
      <description>add Custom Configurations</description>
      <init-params>
        <value-param>
          <name>myCustomCache</name>
          <value>jar:/conf/portal/custom-cache-configuration.xml</value>
        </value-param>         
      </init-params>
    </component-plugin>    
  </external-component-plugins> 
  ...   
</configuration>

In the example above, I call the method addConfig(ExoCacheFactoryConfigPlugin plugin) on the current implementation of ExoCacheFactory which is actually the infinispan implementation.

In the init-params block, you can define a set of value-param blocks and for each value-param, we expect the name of cache that needs a specific configuration as name and the location of your custom configuration as value.

In this example, we indicates to the factory that we would like that the cache myCustomCache use the configuration available at jar:/conf/portal/custom-cache-configuration.xml.

The factory for infinispan, delegates the cache creation to ExoCacheCreator that is defined as below:

package org.exoplatform.services.cache.impl.infinispan;

...
public interface ExoCacheCreator {
   /**
    * Creates an eXo cache according to the given configuration {@link org.exoplatform.services.cache.ExoCacheConfig}
    * @param config the configuration of the cache to apply
    * @param confBuilder the configuration builder of the infinispan cache
    * @param cacheGetter a {@link Callable} instance from which we can get the cache
    * @exception ExoCacheInitException if an exception happens while initializing the cache
    */
   public ExoCache<Serializable, Object> create(ExoCacheConfig config, ConfigurationBuilder confBuilder, 
            Callable<Cache<Serializable, Object>> cacheGetter) throws ExoCacheInitException;
   /**
    * Returns the type of {@link org.exoplatform.services.cache.ExoCacheConfig} expected by the creator  
    * @return the expected type
    */
   public Class<? extends ExoCacheConfig> getExpectedConfigType();
   /**
    * Returns a set of all the implementations expected by the creator. This is mainly used to be backward compatible
    * @return the expected by the creator
    */
   public Set<String> getExpectedImplementations();
}

The ExoCacheCreator allows you to define any kind of infinispan cache instance that you would like to have. It has been designed to give you the ability to have your own type of configuration and to always be backward compatible.

In an ExoCacheCreator, you need to implement 3 methods which are:

By default, no cache creator are defined, so you need to define them yourself by adding them in your configuration files.

This is the generic cache creator that allows you to use any eviction strategies defined by default in Infinispan.


..
<object-param>
  <name>GENERIC</name>
  <description>The generic cache creator</description>
  <object type="org.exoplatform.services.cache.impl.infinispan.generic.GenericExoCacheCreator">
    <field name="implementations">
      <collection type="java.util.HashSet">
         <value>
            <string>NONE</string>
         </value>
         <value>
            <string>FIFO</string>
         </value>
         <value>
            <string>LRU</string>
         </value>
         <value>
            <string>UNORDERED</string>
         </value>
         <value>
            <string>LIRS</string>
         </value>
      </collection>        
    </field>
    <field name="defaultStrategy"><string>${my-value}</string></field>
    <field name="defaultMaxIdle"><long>${my-value}</long></field>
    <field name="defaultWakeUpInterval"><long>${my-value}</long></field>
  </object>
</object-param>
...

All the eviction strategies proposed by default in infinispan rely on the generic cache creator.


...
       <object-param>
        <name>myCache</name>
        <description>My cache configuration</description>
        <object type="org.exoplatform.services.cache.impl.infinispan.generic.GenericExoCacheConfig">
          <field name="name"><string>myCacheName</string></field>
          <field name="strategy"><int>${my-value}</int></field>
          <field name="maxEntries"><long>${my-value}</long></field>
          <field name="lifespan"><long>${my-value}</long></field>
          <field name="maxIdle"><long>${my-value}</long></field>
          <field name="wakeUpInterval"><long>${my-value}</long></field>
        </object>
      </object-param> 
...

  • Old configuration


...
      <object-param>
        <name>myCache</name>
        <description>My cache configuration</description>
          <field name="name"><string>lru-with-old-config</string></field>
          <field name="maxSize"><int>${my-value}</int></field>
          <field name="liveTime"><long>${my-value}</long></field>
          <field name="implementation"><string>${my-value}</string></field>
        </object>
      </object-param> 
...

Note

For the fields maxIdle and wakeUpInterval needed by infinispan, we will use the default values provided by the creator.

In order to be able to use infinispan in distributed mode with the ability to launch external JVM instances that will manage a part of the cache, we need to configure the DistributedCacheManager. In the next sections, we will show how to configure the component and how to launch external JVM instances.

The DistributedCacheManager is the component that will manage all the cache instances that we expect to be distributed, it must be unique in the whole JVM which means that it must be declared at RootContainer level in portal mode or at StandaloneContainer in standalone mode. See below an example of configuration.


<component>
  <type>org.exoplatform.services.ispn.DistributedCacheManager</type>
  <init-params>
    <value-param>
      <name>infinispan-configuration</name>
      <value>jar:/conf/distributed-cache-configuration.xml</value>
    </value-param>
    <properties-param>
      <name>parameters</name>
      <description>The parameters of the configuration</description>
      <property name="configurationFile" value="${gatein.jcr.jgroups.config}"></property>
      <property name="invalidationThreshold" value="0"></property>
      <property name="numOwners" value="3"></property>
      <property name="numVirtualNodes" value="2"></property>
    </properties-param>     
  </init-params>
</component>

As described above, the configuration of infinispan must defined explicitly each cache using the nameCache block no dynamic configuration of cache is supported. Indeed to ensure that the whole cluster is consistent in term of defined cache, it is required to configure all the cache that you will need and register it using its future name.

For now, we have 2 supported cache name which are JCRCache and eXoCache. JCRCache is the name of the cache that we use in case we would like to store the data of the JCR into a distributed cache. eXoCache is the name of the cache that we use in case we would like to store the data of some eXo Cache instances into a distributed cache.

See below an example of infinispan configuration with both eXoCache and JCRCache defined:

<infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:infinispan:config:5.1 http://www.infinispan.org/schemas/infinispan-config-5.1.xsd"
  xmlns="urn:infinispan:config:5.1">
   <global>
      <globalJmxStatistics jmxDomain="exo" enabled="true" allowDuplicateDomains="true"/>
      <transport transportClass="org.infinispan.remoting.transport.jgroups.JGroupsTransport" clusterName="JCR-cluster" distributedSyncTimeout="20000">
        <properties>
          <property name="configurationFile" value="${configurationFile}"/>
        </properties>
      </transport>
      <shutdown hookBehavior="DEFAULT"/>
   </global>
   <namedCache name="JCRCache">
      <locking isolationLevel="READ_COMMITTED" lockAcquisitionTimeout="120000" writeSkewCheck="false" concurrencyLevel="500" useLockStriping="true" />
      <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.GenericTransactionManagerLookup" syncRollbackPhase="true" syncCommitPhase="true" eagerLockSingleNode="true" transactionMode="TRANSACTIONAL"/>
      <jmxStatistics enabled="true"/>
      <clustering mode="distribution">
        <l1 enabled="true" invalidationThreshold="${invalidationThreshold}"/>
         <hash numOwners="${numOwners}" numVirtualNodes="${numVirtualNodes}" rehashRpcTimeout="120000">
           <groups enabled="true"/>
         </hash>
         <sync replTimeout="180000"/>
      </clustering>
   </namedCache>
   <namedCache name="eXoCache">
      <locking isolationLevel="READ_COMMITTED" lockAcquisitionTimeout="120000" writeSkewCheck="false" concurrencyLevel="500" useLockStriping="true" />
      <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.GenericTransactionManagerLookup" syncRollbackPhase="true" syncCommitPhase="true" eagerLockSingleNode="true" transactionMode="TRANSACTIONAL"/>
      <jmxStatistics enabled="true"/>
      <clustering mode="distribution">
         <l1 enabled="true" invalidationThreshold="${invalidationThreshold}"/>
         <hash numOwners="${numOwners}" numVirtualNodes="${numVirtualNodes}" rehashRpcTimeout="120000"/>
         <sync replTimeout="180000"/>
      </clustering>
   </namedCache>
</infinispan>

In case you intend to use the distribued mode, you can launch external JVM in standalone mode to provide more memory to your current cache. To do so, you will need to get the file of type exo.jcr.component.core.impl.infinispan.v5-binary.zip in which you will find scripts to launch your cache servers. These scripts allow optional arguments that are described below:

help|?|<configuration-file-path>|udp|tcp <initial-hosts>


Note

If you intend to use the CacheServer in order to manage some of your eXo Cache instances, don't forget to add the jar files that define both the keys and the values in the lib directory of the CacheServer distribution and restarts your CacheServer instances otherwise the unmarshalling will fail with java.lang.ClassNotFoundException.

Copyright © 2009-2012. All rights reserved. eXo Platform SAS