Cody S Cody S - 13 days ago 4
Java Question

How to dynamically load values into Tomcat's Context XML file

Given that Tomcat's Context XML file tends to contain sensitive information (often including credentials needed to connect to a Database), how can I dynamically load these values from a source other than the plain-text context.xml?

Answer

Say you have a tomcat/conf/context.xml file that looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <Resource 
            name="jdbc/MyDB" 
            auth="Container" 
            type="javax.sql.DataSource" 
            removeAbandoned="true" 
            removeAbandonedTimeout="15" 
            maxActive="5" 
            maxIdle="5" 
            maxWait="7000" 
            username="${db.mydb.uid}"
            password="${db.mydb.pwd}"
            driverClassName="${db.mydb.driver}"
            url="${db.mydb.url}${db.mydb.dbName}?autoReconnectForPools=true&amp;characterEncoding=UTF-8"
            factory="com.mycompany.util.configuration.CustomDataSourceFactory"
            validationQuery="SELECT '1';"
            testOnBorrow="true"/>
</Context>

What we want to replace in this case is anything in the ${.*} stuff in this resource definition. With slight modification to the code below, however, you can perform these substitutions on pretty much whatever criteria you'd like.

Notice the line factory="com.mycompany.util.configuration.CustomDataSourceFactory"

What this means is that Tomcat will attempt to use this factory to process this resource. It should be mentioned that this means that this factory will have to be on Tomcat's classpath on startup (Personally, I put mine in a JAR in the Tomcat lib directory).

Here is what my factory looks like:

package com.mycompany.util.configuration;

import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class CustomDataSourceFactory extends BasicDataSourceFactory implements ObjectFactory {

    private static final Pattern _propRefPattern = Pattern.compile("\\$\\{.*?\\}");

    //http://tomcat.apache.org/tomcat-6.0-doc/jndi-resources-howto.html#Adding_Custom_Resource_Factories
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        if (obj instanceof Reference) {
            Reference ref = (Reference) obj;
            System.out.println("Resolving context reference values dynamically");

            for(int i = 0; i < ref.size(); i++) {
                RefAddr addr = ref.get(i);
                String tag = addr.getType();
                String value = (String) addr.getContent();

                Matcher matcher = _propRefPattern.matcher(value);
                if (matcher.find()) {
                    String resolvedValue = resolve(value);
                    System.out.println("Resolved " + value + " to " + resolvedValue);
                    ref.remove(i);
                    ref.add(i, new StringRefAddr(tag, resolvedValue));
                }
            }
        }
        // Return the customized instance
        return super.getObjectInstance(obj, name, nameCtx, environment);
    }

    private String resolve(String value) {
        //Given the placeholder, do stuff to figure out what it's true value should be, and return that String.
        //This could be decryption, or maybe using a properties file.
    }
}

Then, once this code is on the classpath, restart Tomcat and watch catalina.out for the log messages. NOTE: The System.out.println statements will likely end up printing sensitive information to your logs, so you may want to remove them once you are done debugging.

On a sidenote, I wrote this out because I found that many examples were too specific to one specific topic (such as utilizing cryptography), and I wanted to show how this can be done generically. Furthermore, some of the other answers to this question don't explain themselves very well, and I had to do some digging to figure out what needed to be done to make this work. I wanted to share my findings with you guys. Please feel free to comment on this, asking any questions, or making corrections if you find problems, and I'll be sure to roll the fixes into my answer.