Grails Infinispan Plugin Manual
The Grails Infinispan Plugin plugin provides the developer with the ability to quickly configure Infinispan and register cache managers and caches as Spring beans. Additionally this plugin contains a tag library which developers can use to interact with caches directly on Groovy Server Pages.
Note that the 0.4.3 release is the last release that will focus on the Infinispan 4.x series – subsequent releases will all target Infinispan 5.x releases. Note also that Infinispan 5.x requires a newer version of Hibernate core than Grails 1.3.x series ships with, so your options include working with the Grails Infinispan Plugin version 0.4.3 until Grails 1.4 is available or manually patch Grails and see what happens.
Commercial Support & Update Notifications (via Twitter)
If you have a question or would you like a new feature we would be happy to hear from you so send us an email and let us know!
giplugin is the official Grails Infinispan Plugin Twitter feed which is used to notify users of new releases and other information — for updates, follow us using the button below.
Contact Red Hat for commercial Infinispan support. The article Red Hat Introduces JBoss Enterprise Data Grid on the Wall Street Journal web site has additional information about this service.
Plugin Documentation, Test Reports, and Source Code
Plugin Test / Demo Applications
For Grails pre 2.0.0 and Infinispan pre 5.0.0 click here. Instructions for how to run this application and interesting parts of the application worth reviewing are included in the *readme.txt* file available in the application’s root directory.
For Grails 2.0.0 and Infinispan 5.0.0 click here. Instructions for how to run this application and interesting parts of the application worth reviewing are included in the readme.txt file available in the application’s root directory.
Installing the Grails Infinispan Plugin
Usage:
C:/…/workspace/InfinispanTestApp>grails install-plugin infinispan
Note that Grails does not allow plugins to modify some files – of particular importance here is the DataSource.groovy file which, by default, configures EhCache as the second level cache.
If you install this plugin you will need to manually modify the settings in the DataSource.groovy file as follows:
Remove the following:
hibernate {
cache.use_second_level_cache = true
cache.use_query_cache = true
cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
}
And replace with:
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.region.factory_class='org.hibernate.cache.infinispan.InfinispanRegionFactory'
}
For more information, refer to this article.
Configuring the Grails Infinispan Plugin
In the example below we see a development environment and a production environment. The development environment reads the configuration from a file whereas the production environment uses the globalConfiguration and configuration variables.
def globalConfiguration = new GlobalConfiguration ()
def configuration = new Configuration ()
globalConfiguration.asyncTransportExecutorFactoryClass =
"org.infinispan.executors.DefaultExecutorFactory"
globalConfiguration.asyncTransportExecutorProperties =
[maxThreads:1] as Properties
configuration.indexingEnabled = true
configuration.indexLocalOnly = true
def queryHelperProperties = ["hibernate.search.default.directory_provider" :
"org.hibernate.search.store.RAMDirectoryProvider"] as Properties
environments {
development {
infinispan = {
register {
cacheManager {
named "matrixCacheManager"
configured {
externally {
using "grails-app/conf/infinispan/workingQuery.xml"
}
}
caches "agentCache", "illinoisCitiesCache"
}
queryHelper {
named "agentQueryHelper"
referencing "agentCache"
properties queryHelperProperties
classes infinispantestapp.Agent, infinispantestapp.SuperHero
}
}
}
}
production {
infinispan = {
register {
cacheManager {
named "matrixCacheManager"
configured {
declaratively {
using globalConfiguration
using configuration
}
}
caches "agentCache", "illinoisCitiesCache"
}
queryHelper {
named "agentQueryHelper"
referencing "agentCache"
properties queryHelperProperties
classes infinispantestapp.Agent, infinispantestapp.SuperHero
}
}
}
}
}
The following example demonstrates how the cacheManager can be configured from an xml file. Note that the path to the file in the development environment is slightly different than the path in the production environment. For example, in development the path would look like file “grails-app/conf/infinispan/minimal.xml”, whereas in production it would be file “infinispan/minimal.xml”.
cacheManager {
named "fooCacheManager"
configured {
externally {
using "grails-app/conf/infinispan/minimal.xml"
}
}
caches "foo"
}
There is a precondition required by Infinispan that demands that an instance of QueryHelper be set prior to executing a query. This comes as part of the configuration DSL and ensures that the QueryHelper(s) are created before any items are put into the cache. This prevents the developer from misconfiguring the QueryHelper, in which case zero results will be returned from the cache whenever it’s queried.
Refer to this page and this page for more information. This requirement should be removed from the Infinispan version 5.0 release.
Warning
Note that the Infinispan cache creation happens through instances of the CacheManager by invoking the getCache method. This means that it is possible to have two caches in an application with the exact same name. This plugin registers the actual cache instance with Spring as a bean with the id given in the cache manager _caches_ set (as illustrated above). If you create more than one cache manager and replicate cache names then the second cache bean will not be registered and an exception will be thrown on application startup. The solution is to always choose unique cache names, OR you will be forced to use the CacheManager to get instances of one of the caches that has a duplicated name.
Using the Cache
In this example we create a controller that loads Agent Smith into the cache 1000 times:
package infinispantestapp
class LoadAgentCacheController {
def agentCache
def index = {
for (int ctr in 1..1000) {
agentCache.put ("person id $ctr", new Agent (name:"Agent Smith", weapon:"gun"))
}
}
}
Note that the cache manager is also registered as a bean and can be used as well, for example:
class AnotherController {
def matrixCacheManager
def index = {
// ...
}
}
Querying the Cache and Iterating over the Results
As of the 0.3.2 plugin release two new methods appear on the Cache via the CacheCapabilities mixin which allows the developer to query the cache directly, for example:
class QueryAgentsController {
def agentCache
def index = {
def results = agentCache.query (Agent.class, "weapon", "gun")
def hits = results.getResultSize()
log.info ("hits: $hits")
// Iterate over the cache query using an eager iterator and passing each item to the closure.
// Note that the fetch size is set to 1 by default (this is the same value that Infinispan
// defaults to).
results.each {
log.info ("it: $it")
}
// Iterate over the cache query using a lazy iterator and passing each item to the closure.
// Note that the fetch size is also set to 1 by default.
results.each (true, {
log.info ("it: $it")
})
// Iterate over the cache query using an eager iterator and passing each item to the closure.
// Note that the fetch size in this example is set to 25.
results.each (25, {
log.info ("it: $it")
})
// Iterate over the cache query using a lazy iterator and passing each item to the closure.
// Note that the fetch size in this example is set to 25.
results.each (true, 25, {
log.info ("it: $it")
})
}
}
In order for querying to work, indexing will need to be enabled in the Infinispan XML configuration file; a simple example appears below.
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:infinispan:config:4.0 http://www.infinispan.org/schemas/infinispan-config-4.0.xsd"
xmlns="urn:infinispan:config:4.0">
<default>
<indexing enabled="true" indexLocalOnly="true" />
</default>
</infinispan>
For more information refer to the QueryCapabilities mixin as well as the QueryCapabilitiesTest unit test.
Using Closures as Listeners
Available in the 0.3.4 release of this plugin are methods that allow the developer to add closures as listeners to the EmbeddedCacheManager and the Cache.
See here for a list of listeners that are available.
Events dispatched to these listeners are delivered synchronously and asynchronously; refer to the section entitled “Threads and notification dispatching” in this article for more detail about the impact this can have.
Warning
Note that the listeners available to the EmbeddedCacheManager differ from those available to the Cache.
The “on” methods all follow the same format:
- on
(Closure closure) // Defaults to synchronous delivery. - on
(boolean synchronous, Closure closure)
There are also methods available for removing listeners — these follow the following format:
- remove
Listener (Closure closure) // Removes the closure that was registered to receive events synchronously. - remove
Listener (boolean synchronous, Closure closure) - remove
Events that the EmbeddedCacheManager accepts:
- CacheStartedEvents – Use the onCacheStarted or removeCacheStartedListener methods.
- CacheStoppedEvents – Use the onCacheStopped or removeCacheStoppedListener methods.
- ViewChangedEvents – Use the onViewChanged or removeViewChangedListener methods.
Events that the Cache accepts:
- CacheEntryModifiedEvents – Use the onCacheEntryModified or removeCacheEntryModifiedListener methods.
- CacheEntryCreatedEvents – Use the onCacheEntryCreated or removeCacheEntryCreatedListener methods.
- CacheEntryRemovedEvents – Use the onCacheEntryRemoved or removeCacheEntryRemovedListener methods.
- CacheEntryVisitedEvents – Use the onCacheEntryVisited or removeCacheEntryVisitedListener methods.
- CacheEntryLoadedEvents – Use the onCacheEntryLoaded or removeCacheEntryLoadedListener methods.
- CacheEntryEvictedEvents – Use the onCacheEntryEvicted or removeCacheEntryEvictedListener methods.
- CacheEntryActivatedEvents – Use the onCacheEntryActivated or removeCacheEntryActivatedListener methods.
- CacheEntryPassivatedEvents – Use the onCacheEntryPassivated or removeCacheEntryPassivatedListener methods.
- TransactionRegisteredEvents – Use the onTransactionRegistered or removeTransactionRegisteredListener methods.
- TransactionCompletedEvents – Use the onTransactionCompleted or removeTransactionCompletedListener methods.
- CacheEntryInvalidatedEvents – Use the onCacheEntryInvalidated or removeCacheEntryInvalidatedListener methods.
A closure that has been registered to receive multiple events can be removed by invoking the *removeAllListeners* method. Note that this applies to both the EmbeddedCacheManager as well as the Cache.
For example:
def loggingClosure = {
log.info ("Cache started: $it")
}
/**
* The default is synchronous delivery, see below for an example of how to receive events asynchronously.
*/
matrixCacheManager.onCacheStarted (loggingClosure)
matrixCacheManager.onCacheStopped (loggingClosure)
/**
* Note that we can remove the listener later by passing the closure to a [new] removeAllListeners method.
*/
matrixCacheManager.removeAllListeners (loggingClosure)
/**
* If the intention is to add the listener permanently, then the closure can be added as follows:
*/
matrixCacheManager.onCacheStopped {
log.info ("Cache stopped: $it")
}
/**
* The default is synchronous delivery, if we want delivery to be asynchronous we need to set
* the synchronous flag to false as follows:
*/
matrixCacheManager.onCacheStopped (false, { /* Process asynchronous events. */})
Using Transactions
Note that the transaction manager is passed into the closure as the transactionManager parameter.
testCache.transactionally {
testCache.remove (DEFAULT_KEY)
if (transactionManager.status == ...) {
// Do something...
}
}
See also javax.transaction.TransactionManager.
Using the Cache Tag Library
Here is a simple example of using the Cache tag library:
<cache:size name="agentCache" var="size"/>
There are ${size} agents in the cache.
Clearing the cache of all agents...<cache:clear name="agentCache"/>
At this time tags are provided for the following Cache methods:
- clear
- containsKey
- get
- isEmpty
- put
- remove
- size
- replace
- getStatus
- getVersion
- putAll
- evict
- getName
- putForExternalRead
- getConfiguration
- startBatch
- endBatch
- query
- compact
Warning
The following methods are not supported:
- containsValue
- keySet
- entrySet
- values
Logging
Add the following to the application’s grails-app\conf\Config.groovy log4j closure to enable logging on Infinispan and the Grails Infinispan Plugin:
warn 'org.infinispan.profiling',
'org.infinispan.jmx',
'org.infinispan.factories'
info 'org.infinispan'
debug 'com.coherentlogic'
Dependencies
The Grails Infinispan plugin contains the following dependencies:
Infinispan-4.2.1.FINAL (Ursus)
- infinispan-core.jar
- jboss-common-core-2.2.14.GA.jar
- jgroups-2.12.0.CR5.jar
- marshalling-api-1.2.3.GA.jar
- river-1.2.3.GA.jar
Infinispan Query Module
- hibernate-commons-annotations-3.2.0.Final.jar
- hibernate-search-3.2.1.Final.jar
- infinispan-query.jar
- lucene-analyzers-2.9.3.jar
- lucene-core-2.9.3.jar
Source: Infinispan
Hibernate Infinispan version 3.5.1-Final
- hibernate-infinispan-3.5.1-Final.jar
Source: Hibernate Infinispan