CacheManager's diskStorePath

Lately I had to configure the Ehcache [0] CacheManager's diskStorePath property. That alone wouldn't be a problem, but I had to come up with a mechanism that allowed to set the diskStorePath based on customer-specific configuration settings. As I couldn't find proper instructions on the web or on stackoverflow, I thought it would be worth sharing the outcome.

The problem

The Grails plugin collective (GPC) offers the so-called "Spring-Cache Plugin" [1], a great plugin by Robert Fletcher. The plugin comes with a @Cacheable annotation. Whenever a type, method or field is annotated as "cacheable" the result is put into a Ehcache instance and further calls might return a cached result (depending on the cache settings). Let's start with the default settings. The default Ehcache cache setting look like this (extracted from ehcache-failsafe.xml):
 
<defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
 
The attribute overflowToDisk is true, meaning that whenever the maximum number of in-memory objects is reached, objects are swapped to disk. However, diskPersistent is false, meaning that between subsequent server/application restarts, the in-memory cache is lost and all swapped objects will be cleaned on startup. Our requirement was to activate disk persistency and configure a diskStorePath, based on a given customer configuration. The customer configuration diskStore directory path would be based on the build settings, ie. a custom Config.groovy file. Specifying a custom diskStorePath would be done in a custom ehcache.xml file like this:
 
<diskStore path="here comes the customer-specific directory"/>
 

Setting the diskStorePath

The Springcache plugin works with Spring's EhCacheManagerFactoryBean and EhCacheFactoryBean. Given the following configuration in Config.groovy ...
 
springcache.enabled = true

springcache {
    defaults {
        // set default cache properties that will apply to all caches that do not override them

        eternal = false
        diskPersistent = true
        maxElementsInMemory = 1000
        maxElementsOnDisk = 10000

//        24 hours

        timeToIdle = 86400
        timeToLive = 86400

        overflowToDisk = true
        memoryStoreEvictionPolicy = "LRU"
    }

    caches {
        myCache {
            // set any properties unique to this cache

        }
        // more caches follow ...

}
 
... the plugin will create one or multiple EhCacheFactoryBean instances, by iterating over springcache.defaults.
 
    springcacheDefaultCache(EhCacheFactoryBean) { bean ->
        bean."abstract" = true
        cacheManager = ref("springcacheCacheManager")
        application.config.springcache.defaults.each {
            bean.setPropertyValue it.key, it.value
        }
    }

    application.config.springcache.caches.each {String name, ConfigObject cacheConfig ->
        "$name"(EhCacheFactoryBean) {bean ->
            bean.parent = springcacheDefaultCache
            cacheName = name
            cacheConfig.each {
                bean.setPropertyValue it.key, it.value
            }
        }
    }
 
The diskStorePath is actually held by Ehcache's CacheManager. It is a singleton managing the default cache and all the other cache instances. Bad news is that EhcacheManagerFactoryBean does not provide a way to define a diskStorePath [2]. The only way to configure a custom path is to reference a custom ehcache.xml configuration:
 
public class EhCacheManagerFactoryBean {   
    // ...

    public void setConfigLocation(org.springframework.core.io.Resource configLocation)
}
 
As we have multiple customers and therefore potentially multiple diskStore paths and wanted to control the path through the customer-specific Config.groovy, we had to find a way to dynamically generate a temporary ehcache.xml. As it turns out, with Groovy's XML support this is a pretty straight-forward process in resources.groovy:
 
springcacheCacheManager(EhCacheManagerFactoryBean) {
         // ... set other props


        def writer = new StringWriter()
        def xml = new MarkupBuilder(writer)

        xml.ehcache() {
          diskStore(path: config.app.cache.diskStorePath ?: 'java.io.tmpdir/springcache')
          defaultCache(config.springcache.defaults)
        }

        def ehCacheXmlTempFile   = File.createTempFile("ehcache-springcache", ".xml")
        ehCacheXmlTempFile.setText(writer.toString())

        // set the spring bean property

        configLocation = new FileSystemResource(ehCacheXmlTempFile)
}
 
As you can see from the code snippet above, XmlSlurper is used to slurp a ehcache.xml configuration template, without any predefined settings. As the Springcache plugin has already been configured in Config.groovy, it 'synchronizes' those settings and injects all properties into the slurped DOM. The best thing is that diskStorePath can be set accordingly to the current configuration setting in config.app.cache.diskStorePath, defaulting to the default temporary directory. Last, StreamingMarkupBuilder is used to write the resulting XML to a newly created temporary file.

Conclusion

This article shows how using a dynamic programming language for bean definitions is just awesome and how Groovy's excellent XML support can be used in places XML gurus only can dream of.

[0] Ehcache - http://ehcache.org/
[1] Grails Springcache Plugin
[2] Spring JIRA - [EhCacheFactoryBean] diskStorePath property has no effect