vrijdag 16 januari 2015

Adding a custom cache to Liferay

Suppose you want to implement the following scenario:
  • a user needs access to a list of items from an external system
  • getting the list is a possibly time-consuming task
  • the list can contain many different items
  • the user needs to be able to paginate and sort the list, preferably without re-loading the entire list
It's clear we'll need a cache of some sort to meet this requirement. Luckily Liferay offers many different caching strategies. Let's look at the most obvious choice: the WebCachePool. It's a fairly simple cache with a rather odd implementation design: the cached object itself is responsible to get its own value. Yes, I know,  that sound weird.
Let's look at the code:

String key = "my.cache.key.123";
WebCacheItem cacheableItem = new CustomWebCacheItem(key);
WebCachePoolUtil.get(key, cacheableItem);

where CustomWebCacheItem must implement the WebCacheItem interface like so:


public class CustomWebCacheItem implements WebCacheItem {

 private static final long CACHE_EXPIRY_IN_MS = Time.MINUTE * 5;
 
 @Override
 public Object convert(String key) throws WebCacheException {
  return getList(key);
 }

 private List getList(String key) {
  return getTheListFromSomewhere(key);
 }
 
 @Override
 public long getRefreshTime() {
  return CACHE_EXPIRY_IN_MS;
 }
}

Now the first time we try to get an object from the cache, it will not be there yet. In that case, the cache invokes the method convert(key) and the List is retrieved from somewhere, put into the cache, and returned to the invoker. To me it seems a bit odd that the logic for obtaining the values to be cached would be placed in the cached item itself, but I suppose you could also argue that this is nice because it hides these implementation details of getting the list AND caching it at the same time.

However, this approach will not work when the list cannot be retrieved in one single call but rather is the result of an asynchronous process. In that case the architecture becomes a bit more complicated and we'll need to be able to actively store a result into the cache at the end of the asynchronous process. This functionality is also available in Liferay but you need to dig one level deeper.

Enter SingleVMPool and MultiVMPool.

As their name implies, these can be used in either a standalone Liferay deployment (SingleVMPool) or in a cluster (MultiVMPool)

The nice thing about the SingleVMPool is that it comes with a default configuration. So you can just ask for a cache with your favorite name and start putting values into it:


SingleVMPoolUtil.getCache("my-custom-cache").put("key","value");
String value = (String) SingleVMPoolUtil.getCache("my-custom-cache").get("key");

The not so nice thing about the SingleVMPool is that is comes with a default configuration only.

If you want to change, for example, the expiration time of the cached objects, you're out of luck. Unless, and this is where we finally get to the real subject of this post, unless you add a custom cache yourself.

Like many things Liferay, it's really easy once you know how to do it:

  • get the Liferay sources
  • lookup the file liferay-single-vm.xml and create a copy
  • add your custom cache definition under your favorite name, for example:
<cache
eternal="false"
maxElementsInMemory="10000"
name="my-custom-cache"
overflowToDisk="false"
timeToIdleSeconds="30"
/>
  • be sure to leave the remainder of the file unchanged - the other cache definitions are used by Liferay itself
  • copy this file to your liferay installation: /tomcat/webapps/ROOT/WEB-INF/classes/META-INF/liferay-single-vm.xml
  • add the following line of configuration to your portal-ext.properties: ehcache.single.vm.config.location=/META-INF/liferay-single-vm.xml
  • restart Liferay
  • deploy your portlet and use the cache with the same code as before:
    SingleVMPoolUtil.getCache("my-custom-cache")
That's it!

This post was largely inspired by the following Liferay forum thread: https://www.liferay.com/community/forums/-/message_boards/message/35072828

Geen opmerkingen: