Friday, July 1, 2011

Spring + Configuration

A real world web application (like any other application) needs to be configured somehow. Sometimes this configuration can be known in the compile-time, but some configuration will be provided by an administrator later.

In order to demonstrate two examples of configuration, I am going to pick the application you saw this post (XML-RPC sample) and modify some parts of it to:
1- Populate the properties files using Maven in the compile-time. Here I will use a plug-in to read SVN meta-data and pass the revision number to the properties files.
2- Read the application's dynamic configuration using JNDI. A JDBC datasource is a very good example.
And, I am not going to write any new code to do so.

There is a full working code for this blog post in here that can be checked out using this command:
svn checkout http://tinywebgears-samples.googlecode.com/svn/trunk/contextsample contextsample

1- Populate the properties files:

First of all we are going to have a properties file to define all we need in (contextsample-services.properties):
# These properties are defined in the master pom.xml file
product.name: ${mavenproperties.product.name}
product.version: ${mavenproperties.product.version}
# These properties are populated by maven-svn-revision-number-plugin
product.svn.revision: ${svnpluginproperties.revision}


Then, we need to ask Maven to filter the resource fiels, so that those ${param} placeholders are replaced by actual values.
     <resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>


We have defined some parameters in the pom.xml file that will be used to filter properties files:
<properties>
<!-- Properties exposed by the application -->
<mavenproperties.product.name>${name}</mavenproperties.product.name>
<mavenproperties.product.version>${version}</mavenproperties.product.version>
</properties>


We also need to use this plug-in to read the SVN meta-data. This plug-in will provide svnpluginproperties.* properties accessed in our configuration.

         <plugin>
<groupid>com.google.code.maven-svn-revision-number-plugin</groupid>
<artifactid>maven-svn-revision-number-plugin</artifactid>
<version>${revisionNumberPluginVersion}</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<entries>
<entry>
<prefix>svnpluginproperties</prefix>
</entry>
</entries>
</configuration>
</plugin>


Now, if you compile your code you will have a contextsample-services.properties file (in the target path) that has the right values for parameters. You might want to read this file in your code, or in your Spring configuration file using a tag like this:

 <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:contextsample-services.properties</value>
</property>
</bean>


This way you can access the values of those parameters as ${product.name}, ${product.version}, and ${product.svn.revision} in the bean definitions.

Please note that the properties file we ended up with will be bundled in the application and is not a good place to put your dynamic configuration, because no one will be able to modify them easily after you made the project artifacts.

2- Read dynamic configuration

There are different ways to achieve this goal, but in this post you will see a very easy way of doing so using JNDI. One advantage of it is that you don't need to declare the path of configuration file anywhere in the application. If your application has so many configuration parameters it is possible to declare the path to a properties file (including those parameters) this way and read the whole file in the way mentioned above. Your application is tied to name of JNDI paremeters which is not a big deal.

You don't need a full J2EE application server to do this since most of the Servlet containers have this capability. The instructions provided here will work on Tomcat 5+, and it is very likely to be different for other application servers.

First of all you need to create a META-INF/context.xml file containing these lines:

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

<!-- Context configuration file for the Data Library XML-RPC services -->

<context docbase="${catalina.home}/webapps/contextsample-services" privileged="true" antiresourcelocking="false" antijarlocking="false">

<!-- Define parameters -->
<environment name="config/environment" value="prod 1" type="java.lang.String" override="true">

</environment></context>


Then, all you need to do is reference the parameters provided by JNDI in your Spring context configuration file:

 <bean id="sampleService" class="com.tinywebgears.samples.service.SampleServiceImpl">
<constructor-arg index="0">
<jee:jndi-lookup name="config/environment">
</jee:jndi-lookup></constructor-arg>
...
</bean>


The trick is that Tomcat will get this context.xml file and copy it somewhere in the configuration directory so that it can be edited easily by the administrator. If the file is already there it won't be overwritten, to avoid accidentally overriding hard work of administrators. Please read this section from Tomcat docs for more details. Here is an excerpt from that document:

Only if a context file does not exist for the application in the <code>$CATALINA_HOME/conf/[enginename]/[hostname]/</code>; in an individual file at <code>/META-INF/context.xml</code> inside the application files. If the web application is packaged as a WAR then <code>/META-INF/context.xml</code> will be copied to <code>$CATALINA_HOME/conf/[enginename]/[hostname]/</code> and renamed to match the application's context path. Once this file exists, it will not be replaced if a new WAR with a newer <code>/META-INF/context.xml</code> is placed in the host's appBase.

Please note that providing an <environment> tag in the context.xml is equivalent of providing an <env-entry> tag in the web.xml file. If you provide both you will probably override your application context configuration file.

UPDATE: If you deploy your application by dumping WAR file in Tomcat's webapps folder, this does not apply to you. Tomcat will undeploy your application first (removing the context configuration as well, dropping all the changes you might have done to it). See this post for a solution.