Enterprise Java

How to build and clear a reference data cache with singleton EJBs, Ehcache and MBeans

In this post I will present how to build a simple reference data cache in Java EE, using singleton EJBs and Ehcache. The cache will reset itself after a given period of time, and can be cleared “manually” by calling a REST endpoint or a MBean method. This post actually builds on a previous post How to build and clear a reference data cache with singleton EJBs and MBeans; the only difference is that instead of the storing of the data in a ConcurrentHashMap<String, Object> I will be using an Ehcache cache, and the cache is able to renew itself by Ehcache means.
 
 
 
 

1. Cache

This was supposed to be a read-only cache with the possibility to flush it from exterior. I wanted to have the cache as a sort of a wrapper on the service providing the actual reference data for the application – AOP style with code !

1.1. Interface

Simple Interface for Reference Data

@Local
public interface ReferenceDataCache {

	/**
	 * Returns all reference data required in the application 
	 */
	ReferenceData getReferenceData();
 
	/**
	 * evict/flush all data from cache 
	 */
	void evictAll();
}

The caching functionality defines two simple methods:

  • getReferenceData() – which caches the reference data gathered behind the scenes from all the different sources
  • evictAll() – method called to completely clear the cache

1.2. Implementation

Simple reference data cache implementation with Ehcache

@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
public class ReferenceDataCacheBean implements ReferenceDataCache {
	
	private static final String ALL_REFERENCE_DATA_KEY = "ALL_REFERENCE_DATA";
	
	private static final int CACHE_MINUTES_TO_LIVE = 100;
	
	private CacheManager cacheManager;
	
	private Cache refDataEHCache = null; 	
	
	@EJB
	ReferenceDataLogic referenceDataService;	

	@PostConstruct
	public void initialize(){		
		
		cacheManager = CacheManager.getInstance();
		CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000);
		cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60);
		
		refDataEHCache = new Cache(cacheConfiguration );
		cacheManager.addCache(refDataEHCache);
	}
	
	@Override
	@Lock(LockType.READ)
	public ReferenceData getReferenceData() {
		Element element = refDataEHCache.get(ALL_REFERENCE_DATA_KEY);
		
		if(element != null){	
			return (ReferenceData) element.getObjectValue();
		} else {
			ReferenceData referenceData = referenceDataLogic.getReferenceData();
			
			refDataEHCache.putIfAbsent(new Element(ALL_REFERENCE_DATA_KEY, referenceData));
			
			return referenceData;
		}		
	}

	@Override
	public void evictAll() {
		cacheManager.clearAll();
	}	
	...........
}

Note:

  • @Singleton – probably the most important line of code in this class. This annotation specifies that there will be exactly one singleton of this type of bean in the application. This bean can be invoked concurrently by multiple threads.

Let’s break now the code into the different parts:

1.2.1. Cache initialization

The @PostConstruct annotation is used on a method that needs to be executed after dependency injection is done, to perform any initialization – in our case is to create and initialize the (eh)cache.

Ehcache initialization

	@PostConstruct
	public void initialize(){		
		
		cacheManager = CacheManager.create();

		CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000);
		cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60);
		
		refDataEHCache = new Cache(cacheConfiguration );
		cacheManager.addCache(refDataEHCache);
	}

Note: Only one method can be annotated with this annotation.

All usages of Ehcache start with the creation of a CacheManager, which is a container for Ehcaches that maintain all aspects of their lifecycle. I use the CacheManager.create() method, which is a factory method to create a singleton CacheManager with default config, or return it if it exists:

 cacheManager = CacheManager.create();

I’ve built then a  CacheConfiguration object by providing the name of the cache (“referenceDataCache”) and number of  the maximum number of elements in memory (maxEntriesLocalHeap), before they are evicted (0 == no limit), and finally I set the default amount of time to live for an element from its creation date:

 CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000); cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60);

Now, with the help of the CacheConfiguration object I programmatically create my reference data cache and add to the CacheManager. Note that Caches are not usable until they have been added to a CacheManager:

 refDataEHCache = new Cache(cacheConfiguration ); cacheManager.addCache(refDataEHCache);

Note:  You can also create the caches in a declarative way: when the CacheManager is created, it creates caches found in the configuration. You can create CacheManager by specifying the path of a configuration file, from a configuration in the classpath, from a configuration in an InputStream or by havind the default ehcache.xml file in your classpath. Take a look at Ehcache code samples for more information.

1.2.2. Get data from cache

@Override
@Lock(LockType.READ)
public ReferenceData getReferenceData() {
	Element element = refDataEHCache.get(ALL_REFERENCE_DATA_KEY);
	
	if(element != null){	
		return (ReferenceData) element.getObjectValue();
	} else {
		ReferenceData referenceData = referenceDataLogic.getReferenceData();
		
		refDataEHCache.put(new Element(ALL_REFERENCE_DATA_KEY, referenceData));
		
		return referenceData;
	}		
}

First I try to get the element from the cache based on its key, and if it’s present in the cache (==null), then if will be received from the service class and placed in cache for future requests.

Note: 

The @Lock(LockType.READ) specifies the concurrency lock type for singleton beans with container-managed concurrency. When set to LockType.READ, it enforces the method to permit full concurrent access to it (assuming no write locks are held). This is exactly what I wanted, as I only need to do read operations. The other more conservative option @Lock(LockType.WRITE), which is the DEFAULT by the way, enforces exclusive access to the bean instance. This should make the method slower in a highly concurrent environment…

1.2.3. Clear the cache

Clear cache

 @Override public void evictAll() { cacheManager.clearAll(); }

The clearAll() method of the CacheManager clears the contents of all caches in the CacheManager, but without removing any caches. I just used it here for simplicity and because I only have one cache I need to refresh.

Note: If you have several caches, that is several cache-names, and want to clear only one you need to use the CacheManager.clearAllStartingWith(String prefix), which clears the contents of all caches in the CacheManager with a name starting with the prefix, but without removing them.

2. How to trigger flusing the cache

The second part of this post will deal with the possibilities of clearing the cache. Since the cache implementation is an enterprise java bean, we can call it either from an MBean or, why not, from a web service.

2.1. MBean

If you are new to Java Management Extensions (JMX) , which is a Java technology that supplies tools for managing and monitoring applications, system objects, devices (e.g. printers) and service oriented networks. Those resources are represented by objects called MBeans (for Managed Bean), I highly recommend you start with this tutorial Trail: Java Management Extensions (JMX)

2.1.1. Interface

The method exposed will only allow the reset of the cache via JMX:

 @MXBean public interface CacheResetMXBean { void resetReferenceDataCache(); }

“An MXBean is a type of MBean that references only a predefined set of data types. In this way, you can be sure that your MBean will be usable by any client, including remote clients, without any requirement that the client have access to model-specific classes representing the types of your MBeans. MXBeans provide a convenient way to bundle related values together, without requiring clients to be specially configured to handle the bundles.” [5]

  2.1.2. Implementation

CacheReset MxBean implementation

@Singleton
@Startup
public class CacheReset implements CacheResetMXBean {
    
	private MBeanServer platformMBeanServer;
    private ObjectName objectName = null;
    	
	@EJB
	ReferenceDataCache referenceDataCache;
	
    @PostConstruct
    public void registerInJMX() {
        try {
        	objectName = new ObjectName("org.codingpedia.simplecacheexample:type=CacheReset");
            platformMBeanServer = ManagementFactory.getPlatformMBeanServer();

            //unregister the mbean before registerting again
            Set<ObjectName> existing = platformMBeanServer.queryNames(objectName, null);
            if(existing.size() > 0){
            	platformMBeanServer.unregisterMBean(objectName);
            }
            
            platformMBeanServer.registerMBean(this, objectName);
        } catch (Exception e) {
            throw new IllegalStateException("Problem during registration of Monitoring into JMX:" + e);
        }
    }	
	
	@Override
	public void resetReferenceDataCache() {
		referenceDataCache.evictAll();

	}
	
}

 Note: 

  • as mentioned the implementation only calls the evictAll() method of the injected singleton bean described in the previous section
  • the bean is also defined as @Singleton
  • the @Startup annotation causes the bean to be instantiated by the container when the application starts – eager initialization
  • I use again the @PostConstruct functionality. Here this bean is registered in JMX, checking before if the ObjectName is used to remove it if so…

2.2. Rest service call

I’ve also built in the possibility to clear the cache by calling a REST resource. This happends when you execute a HTTP POST on the (rest-context)/reference-data/flush-cache:

Trigger cache refresh via REST resource

@Path("/reference-data")
public class ReferenceDataResource {
	
	@EJB
	ReferenceDataCache referenceDataCache;
	
        @POST
	@Path("flush-cache")
	public Response flushReferenceDataCache() {
		referenceDataCache.evictAll();
		
		return Response.status(Status.OK).entity("Cache successfully flushed").build();
	}	
	
	@GET
	@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
	public Response getReferenceData(@QueryParam("version") String version) {
		ReferenceData referenceData = referenceDataCache.getReferenceData();				
		
		if(version!=null && version.equals(referenceData.getVersion())){
			return Response.status(Status.NOT_MODIFIED).entity("Reference data was not modified").build();				
		} else {
			return Response.status(Status.OK)
					.entity(referenceData).build();				
		}
	}	
}

Notice the existence of the version query parameter in the @GET getReferenceData(...) method.  This represents a hash on the reference data and if it hasn’t modified the client will receive a 304 Not Modified HTTP Status. This is a nice way to spare some bandwidth, especially if you have mobile clients. See my post Tutorial – REST API design and implementation in Java with Jersey and Spring, for a detailed discussion around REST services design and implementation.

Note: In a clustered environment, you need to call resetCache(…) on each JVM where the application is deployed, when the reference data changes.

Well, that’s it. In this post we’ve learned how to build a simple reference data cache in Java EE with the help of Ehcache. Of course you can easily extend the cache functionality to offer more granular access/clearing to cached objects. Don’t forget to use LockType.WRITE for the clear methods in this case…

Adrian Matei

Adrian Matei (ama [AT] codingpedia DOT org) is the founder of Podcastpedia.org and Codingpedia.org, computer science engineer, husband, father, curious and passionate about science, computers, software, education, economics, social equity, philosophy.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button