Plugin Configurations in Grails

Perhaps, you have a plugin and you would like to configure it instead of hard coding the settings. Lets say, it is a url for REST service that your plugin consumes. In this post, we cover how to incorporate the plugin configurations into part of the default Grails configurations(conf/Config.groovy). In the second part, we cover how to separate the plugins configurations into a separate configurations file while still part of the application

Configurations From App conf/Config.groovy

First, add the configurations setting in the Grails app configuration file(conf/Config.groovy):

// This default value...
yourConfigurationName.url='http://service.com/restapi/'
...
environments {
  development {
    // configurations when in development environment
    yourConfigurationName.url='http://service.com/restapi/'
  }
}

The configuration name may be as you like. From your plugin, this configurations setting can be accessed as follows:

class NodeDriverProxyService {
    def grailsApplication
    def http

    public someMethod(){
        http = new HTTPBuilder(grailsApplication.config.yourConfigurationName.url)
...

As you see, first we inject grailsApplication and then access your setting via grailsApplicaiton.config

This way it is also possible to externalize plugin settings among the other grails configurations as described in post Externalizing Configurations per Grails Application

Configuration from plugins config file

Perhaps, you like to put plugin configurations into separate configuration file instead Grails default Config.groovy
First, create your configuration file and add the configurations. For example grails-app/conf/myPluginName.groovy:

// This default value...
yourConfigurationName.url=http://service.com/restapi/
...
environments {
  development {
    // configurations when in development environment
    yourConfigurationName.url=http://service.com/restapi/
  }
}

This is exact same as part of the Application configurations. Next, here is how to access from plugin:

GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader())
ConfigObject config
try {
   config = new ConfigSlurper().parse(classLoader.loadClass('myPluginName'))
}
catch (Exception e) {/*
    if exception, set default settings
*/}

//to access
config.yourConfigurationName.url

Here, we load the file and then use Groovy ConfigSlurper utility to parse and access plugin configurations

To summarize, we covered two ways for plugins configurations being incorporated in the application. One as part of the Grails configuration and another as a separate plugin configurations within the application.

Issues

1. grailsApplication is null
Make sure grailsApplication.config is used within a method. i.e:

class someClass{
def grailsApplication
def url = grailsApplication.config.pluginName.url//this is going to be null

public someMethod(){
   url = grailsApplication.config.pluginName.url//this works!!!
  }
}

Useful Links

  • http://groovy.codehaus.org/ConfigSlurper
  • http://stackoverflow.com/questions/843216/configuration-of-grails-plugin

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

Locally Untracked Files in Git While Making a Grails Plugin In-Place

At my current workplace, the grails application is divided into application and an extra plugin. To avoid package, maven-install every single time new change made in plugin that need to be seen in application, I decided to make this plugin in-place. To make it in-place plugin, i had to update BuildConfig.groovy as follows:

...
grails.plugin.location.nameOfPluginPlugin = '../../pluginLocation'
...
plugins {
    ...
    //uncomment plugin dependency
}
...

At the same time, i don’t want these changes go into others developers local repos, so i wanted to untrack BuildConfig.groovy file. Here are steps to untrack file from git repo.

Ignoring File in Git

To ignore file globally,here are steps

Step-1: Create global git ignore file, not tracked in repository that is user-specific

git config --global core.excludesfile pathTo/.gitignore_global
 

Note: You can find different sample git ignore files per technology here

Step-2: Added BuildConfig.groovy in my local global git ignore file as follows

...
/relative/pathToApp/BuildConfig.groovy
...

This will keep it ignored, but we still need it make it be untracked before it can be ignored

Step-3: Untrack the file itself since it is already tracked

git rm --cached BuildConfig.groovy

This makes the file to be ignored and also untracked, however. By pushing to the shared repo others will make this file to be untracked as well, which we don’t want

Ignoring vs Untracked

Ignore will only apply to untracked files. So, in our case, where we need the file to still be tracked, the solution of ignoring described above will not work. Instead, we set the file to be assumed unchanged by Git:

git update-index --assume-unchanged appname/grails-app/conf/BuildConfig.groovy  

By ‘assume unchanged’, the file is ignored and no changes appear in git repo. This makes the grails plugin in-place only for me since the changes are not tracked anymore and ,thus, pushed into the shared main repository

Useful links: