Friday, July 25, 2008

Externalization

So I am working on a project at work using Java 6, Spring ( DI and MVC ), ehcache, SOAP(Axis), ibatis, and some other cool stuff. But that is the core of it. Anyways. Early on some decisions were made to make so decisions based on tracking parameters passed in by the client of the web application. So lets say request parameter foo would have known values of a,s,d,f...N and so on. But these were always going to be known. Well, of course this decision by the TEAM has been forgotten and I needed a way to add flexibility to the application configuration so that when new "foo's" are added, the configuration could be updated to handle it.

My initial reaction was:

"Well, I could have the values/config stored in the database and then just cache them."



But that solution would require days because I would have to create the tables, get them ok'd by the db ops team, have them put in the different environments( test, integration, stage, and then finally production ). Then on top of that I would have to create some sort of tool to manage the values in the table so that they could be updated as needed. Then I would have to secure the the admin page for the config management. And so on and so forth.

Then it occurred to a co-worker and I that we could just externalize the Spring Bean definition and cache it for a set amount of time ( and of course relying on the last good configuration if the new one fails ). The external config for the Spring Bean could then live anywhere ( local file system, or over http on a server somewhere ).

This of course was by far the most flexible and convenient way of dealing with the situation. So here is how it works.

Step 1: Create Factory.


public class ServiceFactoryFactoryContextLoader
implements ApplicationContextAware, IServiceFactoryFactoryContextLoader
{
private ApplicationContext parentContext;
private IServiceFactory serviceFactory;
private String location;

public IServiceFactory getServiceFactory() {
String [] configs = new String[]{location};
ApplicationContext applicationCtx = new ClassPathXmlApplicationContext(configs,true, parentContext );
try {
serviceFactory = (IServiceFactory)applicationCtx.getBean("serviceFactory");
} catch (Exception e) {
Logger.getLogger(this.getClass()).error("Uh... Houston we have a problem... Remote Config is not good", e);
}
return serviceFactory;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException
{
this.parentContext = applicationContext;
}

public void setLocation(String location) {
this.location = location;
}
}


Step 2: Enter the JNDI config for location of your externalized resource in the applicationContext.xml:

<jee:jndi-lookup id="serviceFactoryConfig" startup="true"
name="conf/serviceFactoryConfig"
ref="true"
cache="true"></jee:jndi-lookup>



Step 3: Wire factory up in applicationContext.xml

Set up the ServiceFactoryFactoryContextLoader with the cache proxy of course so that we can cache the retrieval and creation of the object. The property 'location' gets injected with the JNDI config value for the location of the externalized resource. And the cache:proxy allows me t cache any method beginning the 'get'. I am using ehcache for my caching but you can use others if you want/need to.

<bean id="serviceFactoryTarget"
class="com.foo.bar.factory.ServiceFactoryFactoryContextLoader">
<property name="location" ref="serviceFactoryConfig" />
</bean>
<cache:proxy id="serviceFactory" refId="serviceFactoryTarget">
<cache:caching methodName="get*" cacheName="serviceFactoryTarget" />
</cache:proxy>



Step 4: Pull the Bean definition out of the applicationContext.xml and into its own config with the Spring beans tag and xmlns declarations.


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="serviceFactory" class="com.foo.bar.service.ServiceFactory">
<property name="lookups">
<map>
<entry key="default">
<bean class="com.foo.bar.service.ServiceLookupKey">
<property name="var1" value="foo" />
<property name="var2" value="bar" />
<property name="resourceExt" value="default" />
</bean>
</entry>
<entry key="bar">
<bean class="com.foo.bar.service.ServiceLookupKey">
<property name="var1" value="baz" />
<property name="var2" value="xyz" />
<property name="resourceExt" value="bar" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>



And of course you need to define the JNDI values however you want.

So now that the application is configured we can launch the application and once the ServiceFactoryFactoryContextLoader.getServiceFactory() is called the app will make a call out to the externalized location to get the config, load it with the parent context ( which in this case is the applicationContext ) and then you can make the call to get the bean in the externalized config from Step 4 ( "serviceFactory" ). And there it is... the externalized config is now set so that I can host the config where ever I want it; on the local file system somewhere or over http on another server.





No comments: