When Ehcache elements need to have a midnight curfew

I was recently asked to setup Ehcache as a second-level cache for a Java based Seam application. In doing so, I was asked if I could expire entities in the cache at midnight or after being alive for 8 hours(whichever comes first). Ehcache uses “timeToIdleSeconds” and “timeToLiveSeconds” to determine when an element should be expired from the cache. It doesn’t allow you to specify the time of day to expire an element.

One option would be to use a batch job that will expire all the elements in the cache. However, if the app runs on multiple servers, you have to make sure the job runs on both. Secondly, what happens if for some reason the job does not run on one of the servers? Also, if a new server is added, you have to make sure this batch job runs against that new server. Three strikes, you’re out!

I decided a better idea would be to create a custom CacheEventListener. When an element is put into the cache or updated in the cache, I will check to see at what point in time that element will expire. If that point in time is after midnight, I then determine how many seconds exist between now and midnight. Then I just set the “timeToLiveSeconds” with that difference.

First, I create a custom CacheEventListener with a handleExpiration method that implements the approach from above. I use the joda time library which makes working with dates and times a real breeze. Especially if you need to do some date arithmetic which is what I really need to do here.

   

package com.kdmooreconsulting.ehcache;
import static org.joda.time.Seconds.secondsBetween; import net.sf.ehcache.CacheException; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import net.sf.ehcache.event.CacheEventListener; import org.joda.time.DateMidnight; import org.joda.time.DateTime;
public class ExpirationDateCacheEventListener implements CacheEventListener {
@Override public void notifyElementPut(Ehcache cache, Element element) throws CacheException { handleExpiration(cache, element); }
@Override public void notifyElementUpdated(Ehcache cache, Element element) throws CacheException { handleExpiration(cache, element); }
private void handleExpiration(Ehcache cache, Element element) { DateTime expirationDateTime = new DateTime(element.getExpirationTime()); DateMidnight midnightTonight = new DateMidnight().plusDays(1); if (expirationDateTime.isAfter(midnightTonight)){ element.setTimeToLive(secondsBetween(new DateTime(), midnightTonight).getSeconds()); } }
@Override public Object clone() throws CloneNotSupportedException{ throw new CloneNotSupportedException(); } // Other Overrides.... }

In order to associate the custom CacheEventListener to a cache, I first need to create a factory for it. The factory will just
create an instance of my listener so that it can be associated to the cache.


package com.kdmooreconsulting.ehcache;
import java.util.Properties; import net.sf.ehcache.event.CacheEventListener; import net.sf.ehcache.event.CacheEventListenerFactory;
public class ExpirationDateCacheEventListenerFactory extends CacheEventListenerFactory {
@Override public CacheEventListener createCacheEventListener(Properties properties) { return new ExpirationDateCacheEventListener(); } }

Now I need to configure the cache in ehcache.xml to use my custom CacheEventListenerFactory. You could also do this programmaticaly if desired.


    <cache name="entityCache"
           maxElementsInMemory="10000"
           maxElementsOnDisk="1000"
           eternal="false"
           overflowToDisk="true"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="28800" 
           timeToLiveSeconds="28800"
           memoryStoreEvictionPolicy="LFU">
           <cacheEventListenerFactory listenFor="local"
               class="com.kdmooreconsulting.ehcache.ExpirationDateCacheEventListenerFactory"/>  
    </cache> 

As it turns out, it’s not really all that difficult to make sure entities adhere to a midnight curfew. Now making teenagers adhere to one, that is a completely different problem all together.

Leave a Reply

Your email address will not be published. Required fields are marked *