Jun 23, 2010

External Per-Context Variables in Tomcat

It often happens that there’s something that needs to be unique or at least external to a deployment of a web application. Some things like JDBC strings, usernames and passwords spring to mind. Very frequently these are kept in properties or configuration files that get packaged in the WAR file and deployed with applications. This can be difficult to maintain, requiring repackaging and redeploying the application if the values within change.

Using an external file or database-based configuration solves that problem easily enough, but at the very least there needs to be enough information passed to the application to reach that external resource. External files placed in well-known (or at least expected) places, like the /etc folder on most OSs, is a good way to get a few basic properties out of the application. Another is to use an environment variable (or few). Simply setting a property in the OS environment before launching the application server will make the value available with the standard Java call System.getEnv().

But, what if there are multiple copies of the application running? How does each instance know to find its particular settings? Also, how does one prevent the wrong instance from accessing another instance’s settings?

Enter context-sensitive definitions. The discussion here focuses on Tomcat, but with just a little different tweaking, it works also on more robust application servers.

Tomcat allows Context definitions per-application in the TOMCAT_HOME/conf/[engine]/[host]/[application].xml file ((http://tomcat.apache.org/tomcat-6.0-doc/config/context.html). In the element the is allowed. When an application is deployed, a Context file is created in the path as can be found in the Tomcat server.xml. Looking at the default server.xml file, we can see that the Engine is named “Catalina,” and inside it the default Host is named “localhost,” which gives us the path TOMCAT_HOME/conf/Catalina/localhost in which the Context files will be created. In the folder, each deployed application will get its own file, and a special “all applications” file named context.xml.default can be created or used (which only works with this problem if a maximum of one instance of the application per Host is deployed).

There is a little spin that goes on with this magic Context directory that will cause a little bit of trouble when getting started. At the time of application deployment (e.g., copying the WAR file to the TOMCAT_HOME/webapps folder), if the appropriate context.xml file doesn’t exist, one will be copied from the WAR/META-INF/context.xml if it exists, or an empty one will be created if there isn’t one in the WAR file. The file will be named after the deployed application, so if the WAR file is MyApp.war, there will be a MyApp.xml Context file created. This behooves us to deploy the application before adding our value to it, and then edit the resulting created file, or to create the file with all of the context.xml contents in it. Further, if you use the Tomcat manager to deploy the applications, either manually or with an Ant or Maven task, it will fail if the Context file exists before deploying, and undeploying the application will delete that file. This can cause a bit of trouble, too. That’s all a little outside of the scope of this document, but be warned about this trouble!

All of that caveat out of the way, the simple solution to our per-context variable follows the form as thus:
<Context><Environment name="mykey" value="myvalue" type="java.lang.String"/></Context>
However, these aren’t your “standard” environment variables, as would be read by System.getenv(), but are instead JNDI variables that can be defined and read with the fairly trivial code shown (very tersely) below. The above context file (e.g., TOMCAT_HOME/conf/Catalina/localhost/MyApp.xml or TOMCAT_HOME/conf/Catalina/localhost/context.xml.default) would find the value with the following code snippet:
Object object = ((Context)(new InitialContext().lookup("java:comp/env")).lookup("mykey");
It’s important to note that using the Context methods requires handling NamingException, as that will be thrown if the key isn’t found, instead of a simple null as would be returned from System.getEnv() if the key isn’t in the environment.

To allow my app to allow defining the variable in either the system (e.g. “set mykey=myvalue”) or the Context, I made a simple little method:
public String getFromEnvironment(final String name) {
if(name == null) return null;
try {
final Object object = ((Context)(new InitialContext().lookup("java:comp/env"))).lookup(name);
if (object != null) return object.toString();
} catch (final Exception e) {}
return System.getenv(name);

If the value is in neither place, or there is no context, no harm is done–just null is returned.

Let me also share that creating a JUnit test on this is a little bit of a pain. The naming bits used by Tomcat are in a JAR file not in the TOMCAT_HOME/lib, but instead in the TOMCAT_HOME/bin/tomcat-juli.jar file! Just to warn those of you who might need this, and are running into unit test troubles because of it.

About the Author

Object Partners profile.

One thought on “External Per-Context Variables in Tomcat

  1. Mostafa Ali says:

    A very helpful post, exactly what I needed, it also works for embedded tomcat, which is awesome

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Android Development for iOS Developers
Android development has greatly improved since the early days. Maybe you tried it out when Android development was done in Eclipse, emulators were slow and buggy, and Java was the required language. Things have changed […]
Add a custom object to your Liquibase diff
Adding a custom object to your liquibase diff is a pretty simple two step process. Create an implementation of DatabaseObject Create an implementation of SnapshotGenerator In my case I wanted to add tracking of Stored […]
Keeping Secrets Out of Terraform State
There are many instances where you will want to create resources via Terraform with secrets that you just don’t want anyone to see. These could be IAM credentials, certificates, RDS DB credentials, etc. One problem […]
Validating Terraform Plans using Open Policy Agent
When developing infrastructure as code using terraform, it can be difficult to test and validate changes without executing the code against a real environment. The feedback loop between writing a line of code and understanding […]