Tuesday, October 13, 2009

Even more useful: ConfigObjectFactoryBean for Spring



Last week I blogged about a ConfigSlurperPlaceholderConfigurer for Spring. I've now come up with something more useful: a ConfigObjectFactoryBean for Spring!

It just creates a Groovy ConfigObject from the specified arguments (config file locations, environment and default environment).

It's more useful because you can use it to populate Spring's standard PropertyPlaceholderConfigurer and even expose the config properties map as a servlet context attribute using the ServletContextAttributeExporter. To populate the PropertyPlaceholderConfigurer and ServletContextAttributeExporter classes with appropriate beans derived from the ConfigObject (resp, java.util.Properties and java.util.Map), the factory-bean and factory-method attributes of the bean definition are used.

Source code of ConfigObjectFactoryBean can be found below. It includes extensive javadoc with example how to configure it in combination with the PropertyPlaceholderConfigurer and ServletContextAttributeExporter.


import groovy.util.ConfigObject;
import groovy.util.ConfigSlurper;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;

/**
* {@link org.springframework.beans.factory.FactoryBean} that exposes a
* {@link groovy.util.ConfigObject} object loaded using a Groovy
* {@link groovy.util.ConfigSlurper}.
*
* Supports the concept of per environment configuration via "environment"
* and "defaultEnvironment" properties. The "environment" property will be
* typically set using a system property.
*
* Can be used in combination with a
* {@link org.springframework.beans.factory.config.PropertyPlaceholderConfigurer}
* as demonstrated in the example below.
*
* Example XML context definition:
*
* <bean id="configObject" class="com.footdex.beans.factory.config.ConfigObjectFactoryBean">
* <property name="targetClass" value="javax.persistence.Persistence" />
* <property name="targetClass" value="javax.persistence.Persistence" />
* <bean>
*
* <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
* <property name="properties">
* <bean factory-bean="configObject" factory-method="toProperties" />
* </property>
* </bean>
*
* <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
* <property name="driverClassName"><value>${dataSource.driverClassName}</value></property>
* <property name="url"><value>${dataSource.url}</value></property>
* <property name="username"><value>${dataSource.username}</value></property>
* <property name="password"><value>${dataSource.password}</value></property>
* </bean>
*
* Example config.groovy:
*
* dataSource {
* driverClassName = "org.hsqldb.jdbcDriver"
* username = "sa"
* password = ""
* }
* environments {
* development {
* dataSource {
* url = "jdbc:hsqldb:mem:devDB"
* }
* }
* test {
* dataSource {
* url = "jdbc:hsqldb:mem:testDb"
* }
* }
* production {
* dataSource {
* url = "jdbc:hsqldb:file:prodDb;shutdown=true"
* password = "secret"
* }
* }
* }
*
* {@link groovy.util.ConfigObject} properties can be exposed as servlet
* context attribute easily:
*
* <bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
* <property name="attributes">
* <map>
* <entry key="configProperty">
* <bean factory-bean="configObject" factory-method="flatten" />
* </entry>
* </map>
* </property>
* </bean>
*
* And than can be accessed in jsp's like ${configProperty["sample.foo"]}.
*
* @author Marcel Overdijk
* @see #setEnvironment
* @see #setDefaultEnvironment
* @see #setLocations
* @see groovy.util.ConfigObject
* @see groovy.util.ConfigSlurper
*/
public class ConfigObjectFactoryBean implements FactoryBean, InitializingBean {

private String environment;
private String defaultEnvironment;
private Resource[] locations;
private ConfigObject config;

private String getEnvironment() {
if (this.environment == null || this.environment.trim().length() == 0) {
return this.defaultEnvironment;
}
else {
return this.environment;
}
}

public void setEnvironment(String environment) {
this.environment = environment;
}

public void setDefaultEnvironment(String defaultEnvironment) {
this.defaultEnvironment = defaultEnvironment;
}

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

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

@PostConstruct
public void yeah() {
System.out.println("yeah!");
}

@Override
public void afterPropertiesSet() throws Exception {
config = new ConfigObject();
ConfigSlurper configSlurper = new ConfigSlurper(getEnvironment());
for (Resource location : locations) {
config.merge(configSlurper.parse(location.getURL()));
}
}

@Override
public Object getObject() throws Exception {
return this.config;
}

@Override
public Class getObjectType() {
return ConfigObject.class;
}

@Override
public boolean isSingleton() {
return true;
}
}

2 comments:

Luke Daley said...

I like your post construct method.

Marcel Overdijk said...

@Luke, Oops ;-) this can be left out yeah!