Hooking into GORM events from plugin in Grails

we are working on Grails plugin that caches Domain objects in Node.js app. The Grails plugin purpose is to keep the cache current. In this plugin, we listen GORM events, so that once insert or update happens, we can tag the particular instance dirty in the cache. There are two ways to accomplish this that i am aware:

1. Via Groovy metaClass method

In the plugin configuration file plugin/nameOfThePluginGrailsPlugin.groovy we overload the GORM events methods – afterUPdate and afterInsert as follows:

import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU
...
 def doWithDynamicMethods = { ctx ->
        def service = ctx.nodeDriverProxyService
        application.domainClasses.each{ cClass ->
          def isCached = GCU.getStaticPropertyValue(cClass.clazz, "isCached")
          if(isCached){
            cClass.metaClass.afterUpdate = { ->
                service.registerUpdate(delegate.id, delegate.version)
            }
          }
        }
...

This goes throw each of the domain classes in the application and finds those that has property ‘isCached’ set to true. Afterwards, using groovy metaclass overloads the ‘afterUpdate’ method called each time the domain instance updated

Here we also call the service that does the work. Since ‘ctx’ is Spring ApplicationContext, we are able directly access the instance of our service – NodeDriverProxyService

This is probably not recommended approach, because once someone adds the method ‘afterUpdate’ to the Domain class itself, it overwrites our dynamic method. This is reasons why its good to use Custom Event Listeners instead.

2. Custom Event Listener

Instead attaching dynamic method to overwrite Domain callback methods ‘afterUpdate’ and ‘afterInsert’, we create custom event listener in src/Groovy:

class CacheListener extends AbstractPersistenceEventListener{
    def nodeDriverProxyService

    public CacheListener(final Datastore datastore) {
        super (datastore)
    }

    @Override
    protected void onPersistenceEvent(final AbstractPersistenceEvent event) {
        switch(event.eventType) {
            case PostInsert:
                if(event.entityObject?.isCached){
                    nodeDriverProxyService.registerInsert(event.entityObject.id, event.entityObject.version)
                }
                break
            case PostUpdate:
                if(event.entityObject?.isCached){
                    nodeDriverProxyService.registerUpdate(event.entityObject.id, event.entityObject.version)
                }
                break;
        }
    }

    @Override
    public boolean supportsEventType(Class eventType) {
        return true
    }

}

In our custom listener, the method ‘onPersistenceEvent’ is called on all GORM events that we filter to what we interested – PostInsert, PostUpdate
Afterwards, we register the listener to ApplicationContext in pluginNameGrailsPlugin.groovy configuration file as follows:

...
    def doWithApplicationContext = { applicationContext ->
        application.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
            def cacheListener = new CacheListener(datastore)
            cacheListener.nodeDriverProxyService = applicationContext.nodeDriverProxyService 
            applicationContext.addApplicationListener(cacheListener)
        }
    }
...

This registers our listener. Here we also manually inject our custom service NodeDriverProxyService to make it available for our listener to do some work

Summary

Whichever way it was implemented, the new Grails plugin can be installed to any of our Grails applications to cache any domain. Grails make it easy again!

Useful links:

  • http://stackoverflow.com/questions/1956115/hooking-into-grails-domain-object-save
  • http://hartsock.blogspot.com/2008/04/inside-hibernate-events-and-audit.html
  • http://grails.org/doc/latest/guide/GORM.html
  • http://grails.1312388.n4.nabble.com/How-to-enable-dynamic-methods-logging-by-interception-ClassCastException-on-PojoWrapper-td3165109.html

Leave a Reply

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