Configure your external dependencies with Spring & Jndi

How to deal with platform environment (Test, Acceptance, Production etc.) specific variables in Java applications?

Applications often need resources like databases, third-company web services, ldap servers and other external systems. It is common practice to externalize the configuration of such resources. In the case of a database dependency the use of a DataSource (hiding the complexity of configuring and connecting to the database) is a good example of this. The details of the configuration are in most cases platform environment specific. So how do we properly externalize the details of the configuration?

Basically the development team has two options:

  • Build an application and specify the target environment
  • Externalize the variables from the deployable unit

I have seen many development teams using the first option. They use ant, maven or maven2 to build their java application and they use a command parameter to specify the target environment of the build. It usually looks something like this:

$ ant install -Denv=test

or in the case of maven2 using profiles:

$ mvn -P test clean install

There are numerous ways to accomplish this using properties files, xml descriptors, filtering etc. The end result is a deployable unit that can only be deployed in the environment it was built for. I don't like this approach. It is better to have a deployable unit that can be deployed in any environment. This is mainly because the deployment of an application should not be the responsibility of developers. It is the responsibility of the maintenance department and the environment specific variables should be part of the environment and not of the application. The final argument is that developers often have no knowledge about the acceptance or production environmental resources. For instance, how can they possibly know the username/password of the production database? So how do we make the deployable unit independent of the target environment?

First it requires that we don't hardcode any environment specific values in the codebase. This is, apart from the problem we are dealing with, always a good practice. So we use properties files or even better (when using Spring, and you should!) we use placeholders in our application context files. An example: we are using an LDAP server to authenticate our users in combination with the excellent Acegi framework. Let us assume we have a test LDAP server and a production LDAP server.

<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
   <constructor-arg index="0" value="${ldap_address}"/>
</bean>

Spring already has support for resolving placeholders like ${ldap_address} during the runtime initialization of the bean factory. Simply define a PropertyPlaceholderConfigurer in your application context file that uses a properties file for pulling values into bean definitions. However the problem with that approach is that you have to wire the PropertyPlaceholderConfigurer with the location of the properties file. The developer can not make assumptions about the location of the properties file nor is it wise to do so. It would be nice if we had a generic way of wiring a PropertyPlaceholderConfigurer with the correct values. Enter Jndi! If you would have a generic hook to populate the environment of the InitialContext with the values of the placeholders then we could use the Jndi Context to lookup and resolve the specific placeholders. Most application servers have such a hook and I will show how to do this with the excellent JBoss application server.

Using the jndi.properties file in the conf directory of your server configuration you can easily pass the platform environment specific values to the InitialContext. You don't have to hardcode an absolute or relative path into your PropertyPlaceholderConfigurer if you use the jndi.properties file. Continuing our example we would need the following key-value entry for our initialDirContextFactory bean:

ldap_address=ldap://organization.intra.local.test

Add this key-value pair to the jndi.properties file located in the conf directory of your JBoss server configuration and use a customized PropertyPlaceholderConfigurer to resolve your placeholders:

public class JndiPlaceholderConfigurer extends PropertyPlaceholderConfigurer  implements InitializingBean {
    private Map environment;
    public void afterPropertiesSet() throws Exception {
        Context context = new InitialContext();
        environment = context.getEnvironment();
    }
    protected String resolvePlaceholder(String placeholder, Properties props) {
        return (String) this.environment.get(placeholder);
    }
}

With one line of xml code in one of your spring bean definitions files the post-processing of the BeanFactory initialization will take care of resolving the placeholders:

<bean id="propertyConfigurer" class="xxx.xxx.JndiPlaceholderConfigurer"/>

Of course you need a proper procedure set up for communicating the environment specific variables your application depends on to the person/department responsible for deploying the application. Currently I'm looking at the Assembly plugin for Maven 2 to automate the process of delivering more than just the deployable application unit to the people responsible for deploying applications. In my next blog I will elaborate more on the assembly plugin and how to make sure that the application server configuration meets the demands of your application.

Comments (8)

  1. Æde - Reply

    July 20, 2006 at 10:25 am

    I totally agree that you should externalize environment specific values. However I do think that developers sometimes should bother with (environment) specific builds. Sometime you find yourself in a situation where a local development environment or what environment whatsoever doesn't have access to all services or resources needed. The solution is to create mock objects that mimic certain behavior. I don't want to bundle those mock objects in every build. As you mentioned Maven 2 and its profiles are very suitable to create separate builds. When you use Spring (and you should ;)) you most likely also get different bean definition files.

    What are your thoughts?

  2. Okke Harsta - Reply

    July 24, 2006 at 4:06 pm

    Totally agree! We have for instance a development environment where we don't have access to a LDap Server and we most certainly do not want to use the production one. In this case I use a MockLdapServer. I then use a maven2 profile for adding the mock implementation class to the deployable unit (thank god for the ant-plugin:) and overwrite the 'normal' configuration file with the mock one during my build process -using the same profile-. The result is then -unfortunetly- an environemnt specific build, but there are always justifiable exceptions to a perfectly sound rule.

  3. achmat - Reply

    August 1, 2006 at 12:23 pm

    Hi,

    Need some help.
    I'm trying to test the aforementioned Jndi placeholder configuration implemention locally using oc4j. I have placed the jndi.properties file at various places but the substitution does not want to take place.

    Could someone please assist me

    Regards
    Achmat

  4. bigblacknemesis - Reply

    February 8, 2007 at 10:58 pm

    This is a good tip! I am building a brand new Spring web application (which I have not done from the ground up on my own) and in previous projects I've seen this handled different ways, and I like this the best as its very simple.. I'm all about very simple 🙂

  5. mahe - Reply

    June 28, 2007 at 5:05 pm

    Hi Okke Harsta ,

    i am trying to access weblogic 8.1 jms queue from standalone application(using spring ) and am getting javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial

    i did not deployed any thing into Weblogic.

    can you help in this regard.

    thanks,
    mahe

  6. Okke Harsta - Reply

    June 28, 2007 at 10:34 pm

    Hi Mahe,

    The blog was sometime ago (targeting jboss users) and I'm afraid you give me few details to work with, but do you have specified the following in your jndi.properties (with the appropiate client jar files on the classpath):

    java.naming.factory.initial=??????????????????
    java.naming.factory.url.pkgs=????????????

    Cheers,okke

  7. mahe - Reply

    June 29, 2007 at 1:25 pm

    Hi Okke Harsta ,

    Thanks for your timely reply.
    now i am able to get the JNDI Context.

    Thanks & regards,
    mahe

  8. Willllllllllllllllllllll - Reply

    February 29, 2008 at 5:08 am

    We use named placeholders in the Spring config
    file:${this.properties.file}
    file:${that.properties.file}
    file:${another.properties.file}
    and the system admins set up the environment variables in the container startup script. E.g. in Windows
    x_OPTS= -Dthis.properties.file=[some path]
    x_OPTS=%x_OPTS=% -Dthat.properties.file=[some path]
    x_OPTS=%x_OPTS=% -Danother.properties.file=[some path]

    No need for a JNDI-capable container!

Add a Comment