Chapter 10. JSR-107 (JCache) API

10.1. JSR-107 (JCache) API

Starting with Red Hat JBoss Data Grid 6.5 an implementation of the JCache 1.0.0 API ( JSR-107 ) is included. JCache specified a standard Java API for caching temporary Java objects in memory. Caching java objects can help get around bottlenecks arising from using data that is expensive to retrieve (i.e. DB or web service), or data that is hard to calculate. Caching these types of objects in memory can help speed up application performance by retrieving the data directly from memory instead of doing an expensive roundtrip or recalculation. This document specifies how to use JCache with Red Hat JBoss Data Grid’s implementation of the new specification, and explains key aspects of the API.

10.2. Dependencies

The JCache dependencies may either be defined in Maven or added to the classpath; both methods are described below:

10.2.1. Option 1: Maven

In order to use the JCache implementation the following dependencies need to be added to the Maven pom.xml depending on how it is used:

  • embedded:

    <dependency>
        <groupId>org.infinispan</groupId>
        <artifactId>infinispan-embedded</artifactId>
        <version>${infinispan.version}</version>
    </dependency>
    
    <dependency>
        <groupId>javax.cache</groupId>
        <artifactId>cache-api</artifactId>
        <version>1.0.0.redhat-1</version>
    </dependency>
  • remote:

    <dependency>
        <groupId>org.infinispan</groupId>
        <artifactId>infinispan-remote</artifactId>
        <version>${infinispan.version}</version>
    </dependency>
    
    <dependency>
        <groupId>javax.cache</groupId>
        <artifactId>cache-api</artifactId>
        <version>1.0.0.redhat-1</version>
    </dependency>

10.2.2. Option 2: Adding the necessary files to the classpath

When not using Maven the necessary jar files must be on the classpath at runtime. Having these available at runtime may either be accomplished by embedding the jar files directly, by specifying them at runtime, or by adding them into the container used to deploy the application.

Embedded Mode

  1. Download the Red Hat JBoss Data Grid 7.1.0 Library from the Red Hat Customer Portal.
  2. Extract the downloaded archive to a local directory.
  3. Locate the following files:

    • jboss-datagrid-7.1.0-library/infinispan-embedded-8.4.0.Final-redhat-2.jar
    • jboss-datagrid-7.1.0-library/lib/cache-api-1.0.0.redhat-1.jar
  4. Ensure both of the above jar files are on the classpath at runtime.

Remote Mode

  1. Download the Red Hat JBoss Data Grid 7.1.0 Hot Rod Java Client from the Red Hat Customer Portal.
  2. Extract the downloaded archive to a local directory.
  3. Locate the following files:

    • jboss-datagrid-7.1.0-remote-java-client/infinispan-remote-8.4.0.Final-redhat-2.jar
    • jboss-datagrid-7.1.0-remote-java-client/lib/cache-api-1.0.0.redhat-1.jar
  4. Ensure both of the above jar files are on the classpath at runtime.

10.3. Create a local cache

Creating a local cache, using default configuration options as defined by the JCache API specification, is as simple as doing the following:

import javax.cache.*;
import javax.cache.configuration.*;

// Retrieve the system wide cache manager
CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
// Define a named cache with default JCache configuration
Cache<String, String> cache = cacheManager.createCache("namedCache",
      new MutableConfiguration<String, String>());
Warning

By default, the JCache API specifies that data should be stored as storeByValue, so that object state mutations outside of operations to the cache, won’t have an impact in the objects stored in the cache. JBoss Data Grid has so far implemented this using serialization/marshalling to make copies to store in the cache, and that way adhere to the spec. Hence, if using default JCache configuration with Infinispan, data stored must be marshallable.

Alternatively, JCache can be configured to store data by reference. To do that simply call:

Cache<String, String> cache = cacheManager.createCache("namedCache",
      new MutableConfiguration<String, String>().setStoreByValue(false));

10.3.1. Library Mode

With Library mode a CacheManager may be configured by specifying the location of a configuration file via the URL parameter of CachingProvider.getCacheManager. This allows the opportunity to define clustered caches in a configuration file, and then obtain a reference to the preconfigured cache by passing the cache’s name to the CacheManager.getCache method; otherwise local caches can only be used, created from the CacheManager.createCache .

10.3.2. Client-Server Mode

With Client-Server mode specific configurations of a remote CacheManager is performed by passing standard HotRod client properties via properties parameter of CachingProvider.getCacheManager. The remote servers referenced must be running and able to receive the request.

If not specified the default address and port will be used (127.0.0.1:11222). In addition, contrary to Library mode, the first time a cache reference is obtained CacheManager.createCache must be used so that the cache may be registered internally. Subsequent queries may be performed via CacheManager.getCache.

10.4. Store and retrieve data

Even though the JCache API does not extend either java.util.Map or java.util.concurrent.ConcurrentMap it provides a key/value API to store and retrieve data:

import javax.cache.*;
import javax.cache.configuration.*;

CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
Cache<String, String> cache = cacheManager.createCache("namedCache",
      new MutableConfiguration<String, String>());
cache.put("hello", "world"); // Notice that javax.cache.Cache.put(K) returns void!
String value = cache.get("hello"); // Returns "world"

Contrary to standard java.util.Map, javax.cache.Cache comes with two basic put methods called put and getAndPut. The former returns void whereas the latter returns the previous value associated with the key. The equivalent of java.util.Map.put(K) in JCache is javax.cache.Cache.getAndPut(K).

Tip

Even though JCache API only covers standalone caching, it can be plugged with a persistence store, and has been designed with clustering or distribution in mind. The reason why javax.cache.Cache offers two put methods is because standard java.util.Map put call forces implementors to calculate the previous value. When a persistent store is in use, or the cache is distributed, returning the previous value could be an expensive operation, and often users call standard http://docs.oracle.com/javase/7/docs/api/java/util/Map.html#put(K, V)[java.util.Map.put(K)] without using the return value. Hence, JCache users need to think about whether the return value is relevant to them, in which case they need to call javax.cache.Cache.getAndPut(K) , otherwise they can call java.util.Map.put(K) which avoids returning the potentially expensive operation of returning the previous value.

10.5. Comparing java.util.concurrent.ConcurrentMap and javax.cache.Cache APIs

Here is a brief comparison of the data manipulation APIs provided by java.util.concurrent.ConcurrentMap and javax.cache.Cache APIs:

Table 10.1. java.util.concurrent.ConcurrentMap and javax.cache.Cache Comparison

Operationjava.util.concurrent.ConcurrentMap<K,V>javax.cache.Cache<K,V>

store and no return

N/A

void put(K key)

store and return previous value

V put(K key)
V getAndPut(K key)

store if not present

V putIfAbsent(K key, V Value)
boolean putIfAbsent(K key, V value)

retrieve

V get(Object key)
V get(K key)

delete if present

V remove(Object key)
boolean remove(K key)

delete and return previous value

V remove(Object key)
V getAndRemove(K key)

delete conditional

boolean remove(Object key, Object value)
boolean remove(K key, V oldValue)

replace if present

V replace(K key, V value)
boolean replace(K key, V value)

replace and return previous value

V replace(K key, V value)
V getAndReplace(K key, V value)

replace conditional

boolean replace(K key, V oldValue, V newValue)
boolean replace(K key, V oldValue, V newValue)

Comparing the two APIs it can be seen that, where possible, JCache avoids returning the previous value to avoid operations doing expensive network or IO operations. This is an overriding principle in the design of the JCache API. In fact, there is a set of operations that are present in java.util.concurrent.ConcurrentMap, but are not present in the javax.cache.Cache because they could be expensive to compute in a distributed cache. The only exception is iterating over the contents of the cache:

Table 10.2. javax.cache.Cache avoiding returns

Operationjava.util.concurrent.ConcurrentMap<K,V>javax.cache.Cache<K,V>

calculate size of cache

int size()

N/A

return all keys in the cache

Set<K> keySet()

N/A

return all values in the cache

Collection<V> values()

N/A

return all entries in the cache

Set<Map.Entry<K, V>> entrySet()

N/A

iterate over the cache

use iterator() method on keySet, values, or entrySet

Iterator<Cache.Entry<K, V>> iterator()

10.6. Clustering JCache instances

Red Hat JBoss Data Grid implementation goes beyond the specification in order to provide the possibility to cluster caches using the standard API. Given a configuration file to replicate caches such as:

<namedCache name="namedCache">
    <clustering mode="replication"/>
</namedCache>

It is possible to create a cluster of caches using this code:

import javax.cache.*;
import java.net.URI;

// For multiple cache managers to be constructed with the standard JCache API
// and live in the same JVM, either their names, or their classloaders, must
// be different.
// This example shows how to force their classloaders to be different.
// An alternative method would have been to duplicate the XML file and give
// it a different name, but this results in unnecessary file duplication.
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
CacheManager cacheManager1 = Caching.getCachingProvider().getCacheManager(
      URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl));
CacheManager cacheManager2 = Caching.getCachingProvider().getCacheManager(
      URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl));

Cache<String, String> cache1 = cacheManager1.getCache("namedCache");
Cache<String, String> cache2 = cacheManager2.getCache("namedCache");

cache1.put("hello", "world");
String value = cache2.get("hello"); // Returns "world" if clustering is working

// --

public static class TestClassLoader extends ClassLoader {
  public TestClassLoader(ClassLoader parent) {
     super(parent);
  }
}

10.7. Multiple Caching Providers

Caching providers are obtained from javax.cache.Caching using the overloaded getCachingProvider() method; by default this method will attempt to load any META-INF/services/javax.cache.spi.CachingProvider files found in the classpath. If one is found it will determine the caching provider in use.

With multiple caching providers available a specific provider may be selected using either of the following methods:

  • getCachingProvider(ClassLoader classLoader)
  • getCachingProvider(String fullyQualifiedClassName)

To switch between caching providers ensure that the appropriate provider is available in the default classpath, or select it using one of the above methods.

All javax.cache.spi.CachingProviders that are detected or have been loaded by the Caching class are maintained in an internal registry, and subsequent requests for the same caching provider will be returned from this registry instead of being reloaded or reinstantiating the caching provider implementation. To view the current caching providers either of the following methods may be used:

  • getCachingProviders() - provides a list of caching providers in the default class loader.
  • getCachingProviders(ClassLoader classLoader) - provides a list of caching providers in the specified class loader.