Wednesday, 23 July 2014

IBM BPM Caching Using WebSphere DynaCache

The nature of BPM often requires integrations to other systems to retrieve information. Depending on the nature of the integration these can often be slow. Where the same data is being retrieved many times (e.g. reference data) and across user sessions, an application can achieve a performance boost by introducing a caching mechanism.

This article will show you how to use WebSphere's inbuilt DynaCache caching utility to cache IBM BPM objects. I have tested this in 7.5.1 but there is no reason why this would not work on other version of IBM BPM (or the older Lombardi TeamWorks).

This isn't going to be a full introduction to WebSphere's DynaCache utility so if you need more information on setting up and configuring caches you will need to look at the IBM documentation.


Setting Up The Cache

DynaCache provides the ability to perform 2 types of caching: Servlet Caching and Object Caching. For this exercise we are interested in the Object Caching. This provides the ability to cache Java objects. The configuration for the Object cache instances can be found in the WebSphere admin console under Resources > Cache instances > Object cache instances.


You will need to create a new Object cache instance to cache your BPM objects. The cache instances are referred to by JNDI name, so you can create as many cache instances as you require. For this example I have created an Object cache instance called "bpmCache" with a JNDI name of "services/cache/bpmCache" (this is the standard naming convention for JNDI names for cache instances). The rest of the fields can be left as their defaults but you may need to refer to the IBM documentation for DynaCache to get your required configuration.



Accessing the Cache using Java

The Object cache instance is exposed to Java via a com.ibm.websphere.cache.DistributedMap object which is retrieved using the JNDI name. Here is an example of retrieving the DistributedMap (Object cache instance) using the JNDI name:

String jndiName = "services/cache/bpmCache"; 
DistributedMap map = null; 
try {
    InitialContext context = new InitialContext(); 
    map = (DistributedMap) context.lookup(jndiName); 
} catch(NamingException e){
    throw new BPMCacheException("Failed to retrieve the Object Cache Instance with JNDI: " + jndiName); 
}

The DistributeMap is an implementation of the java.util.Map, so items can be added a retrieved using the familiar put and get methods. DisputedMap has 2 versions of the put method: one with the familiar key, value pair and another with some extra parameters for more granular control over the items being cached. The JavaDocs explain what each of these items provide:
public java.lang.Object put(java.lang.Object key,
                            java.lang.Object value,
                            int priority,
                            int timeToLive,
                            int sharingPolicy,
                            java.lang.Object[] dependencyIds)

Exposing the Cache to IBM BPM

To expose the caching functionality to IBM BPM you need to create a JAR file with a class which exposes the relevant methods. To each I pass the JNDI name of the Object cache instance to use (as you may have many).

Here are some of the examples (for a JAR and source code see the Resources section):

/**
* Put an item on the Distributed Map (cache)
*
* @param jndi              String  JNDI name of the Object Cache Instance
* @param key               String  String value representing the key for the item
* @param value             Object  Value to be cached.
* @param priority          int     The priority of the cached item (higher priority items are disposed of last)
* @param timeToLive        int     The number of seconds until the item is invalid
* @param inactivityTime    int     The number of seconds unused before the item is invalid
* @throws BPMCacheException
*/
public void put(String jndi, String key, Object value, int priority, int timeToLive, int inactivityTime) throws BPMCacheException {
    initialiseCacheMap(jndi);

    // Put the value in the Distributed Map
    map.put(key, value, priority, timeToLive, inactivityTime, DEFAULT_SHARING_POLICY, null);
}

/**
* Get an item out of the Distributed Map (cache)
*
* @param jndi      String  JNDI name of the Object Cache Instance
* @param key       String  String value representing the key for the item to retrieve
* @return          The Object relating the the key or null if the key does not exist
* @throws BPMCacheException
*/
public Object get(String jndi, String key) throws BPMCacheException {
    // Call the initialise incase its the first access
    initialiseCacheMap(jndi);
    // Return the value retrieved from the key
    return map.get(key);
}

/**
* Check to see if the provided key is in the Distributed Map
*
* @param jndi      String  JNDI name of the Object Cache Instance
* @param key       String  String value representing the key for the item to retrieve
* @return          true if the key is found, false if the key is not found
* @throws BPMCacheException
*/
public boolean containsKey(String jndi, String key) throws BPMCacheException {
    // Call the initialise incase its the first access
    initialiseCacheMap(jndi);
    // Return if the Distributed Map contains the provided key
    return map.containsKey(key);
}

/**
* Manually invalidate the value represented by the key passed in
*
* @param jndi      String  JNDI name of the Object Cache Instance
* @param key       String  String value representing the key for the item to retrieve
* @throws BPMCacheException
*/
public void invalidate(String jndi, String key) throws BPMCacheException {
    // Call the initialise incase its the first access
    initialiseCacheMap(jndi);
    // Invalidate the cached value represented by the key value
    map.invalidate(key);
}

/**
* Clear the cache
* Empty cache of all elements
*
* @param jndi      String  JNDI name of the Object Cache Instance
* @throws BPMCacheException
*/
public void clearCache(String jndi) throws BPMCacheException {
    // Call the initialise incase its the first access
    initialiseCacheMap(jndi);
    // Clear the map
    map.clear();
}

Finally, you can add the JAR as a managed file and expose the caching functionality through Java Integration Components. For the put and get methods which take/return type of java.lang.Object, you can use the BPM type of ANY. This will allow you to store any complex variable (as they are of type com.lombardisoftware.core.TWObject) in the cache. When the object is retrieved from the cache it will be instantiated automatically into the correct BPM variable type.


Resources

A copy of the JAR and source code can be found on Github.