Wednesday, July 11, 2007

Using Groovy ConfigSlurper to Configure Spring Beans

The latest preview release of Groovy (1.1-beta-2) introduced ConfigSlurper, a new class that enables usage of scripts similar to Java property files, for configuration. ConfigSlurper scripts support native Java types and are structured like a tree. Being Groovy scripts, they also support the regular Groovy features. To put things shortly, ConfigSlurper scripts can be seen as property files on steroids!
Spring offers a couple of configurer objects that allows for configuration of bean properties with Java property files. My goal is to implement a configurer that uses ConfigSlurper scripts instead. As an example (from the Spring reference documentation), using PropertyPlaceholderConfigurer:

<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
Here's the associated property file:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql:production:9002
jdbc.username=sa
jdbc.password=root
With ConfigSlurper we could write something like this:
def normalize(s){
   return s.toUpperCase()
} 

jdbc{
    driverClassName=org.hsqldb.jdbcDriver
    url=jdbc:hsqldb:hsql:production:9002
    username=normalize('sa')
    password=normalize('root')
}
Notice that in addition to the usage of scoping to avoid repetition, we can employ basically any Groovy construct inside the script. Needless to say, this example aims to show some of the possibilities, not to represent a relevant use case.

Integration with Spring

The integration is surprisingly simple thanks to the flexibility of both Spring and Groovy. ConfigSlurper parses a script and produces a ConfigObject. A ConfigObject can be merged with another ConfigObject (coming from another script) and finally transformed to a java.util.Properties.

As a base for our GroovyPlaceholderConfigurer, I'm using PropertyPlaceholderConfigurer. Thus, the usual behaviour of property parsing and resolution can be preserved. We only need to redefine the loadProperties method in order to plug the ConfigSlurper in. Here's the code:

public class GroovyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
  private Resource[] locations;

  @Override
  protected void loadProperties(Properties props) throws IOException {
    ConfigObject configObject = new ConfigObject();
    ConfigSlurper configSlurper = new ConfigSlurper();
    for (Resource l : locations) {
      configObject.merge(configSlurper.parse(l.getURL()));
    }
    props.putAll(configObject.toProperties());
  }

  public void setLocations(Resource[] locations) {
    this.locations = locations;
  }

  public void setLocation(Resource location) {
    this.locations = new Resource[] { location };
  }

}
To use it, we need to define a bean in the application context:

<bean class="cafebabe.GroovyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:config.groovy</value>
            <value>classpath:config2.groovy</value>
        </list>
    </property>
</bean>
That's it!