I want to store configuration for a web project outside of the web project (ear/war file).
The application shouldn't know in which container it's running (WebSphere/JBoss etc.).
What is the best way to handle this?
Is JNDI a clean way? If JNDI can solve my problems, how should I configure it? (Custom Objects?)
In my case are there only simple Key=>Value pairs (String,String) for SOAP/WS endpoints.
See this question for reading properties file outside of the WAR file.
See this question for reading variable values from JNDI. I believe that this is the best solution. You can read a String variable with this code:
Context initialContext = new InitialContext();
String myvar = (String) initialContext.lookup("java:comp/env/myvar");
The above code will work on all containers. In Tomcat you declare the following in conf/server.xml:
<GlobalNamingResources ...>
<Environment name="myvar" value="..."
type="java.lang.String" override="false"/>
</GlobalNamingResources>
The above will create a global resource. It is also possible to define a resource in the context of application. In most containers the JNDI resources are available through a MBeans Management Console. Some of them offer a graphical interface to edit them. At most an application restart is needed, when a change is made.
How JNDI resources are defined and edited is container specific. It is the job of the configurator/administrator to apply the appropriate settings.
These are the benefits offered by JNDI:
- You can define default values of the parameters in the WAR/EAR file.
- Parameters are easily configurable at the container.
- You don't need to restart the container when you modify the value of a parameter.
We had a similar configuration requirement when deploying a webapp for different developers, and on Amazon's EC2: how do we separate configuration from the binary code? In my experience, JNDI is too complex, and varies too much between containers to be used. Also, hand-editing XML is very susceptible to syntax errors, so was the idea was thrown out. We resolved this with a design based on a few rules:
1) only simple name=value entries should be used
2) new configurations should be loadable by changing only one parameter
3) our WAR binary must be reconfigurable w/o repackaging it
4) sensitive parameters (passwords) will never be packaged in the binary
Using .properties files for all configuration, and using System.getProperty("domain");
to load the appropriate properties files, we were able to meet the requirements. However, the system property does not point to a file URL, instead we created a concept we call "domain" to specify the configuration to use. The location of the configuration is always:
$HOME/appName/config/$DOMAIN.properties
.
So if I want to run my app using my own configuration, I start the app by setting the domain to my name:
-Ddomain=jason
on startup, and the app loads the file:
/home/jason/appName/config/jason.properties
This lets developers share configurations so we can recreate the same state of the app for testing and deployment without recompiling or repackaging. The domain value is then used to load .properties from a standard location, outside of the bundled WAR.
I can completely recreate the production environment on my workstation by using the production configuration like:
-Ddomain=ec2
which would load:
/home/jason/appName/config/ec2.properties
This setup allows us to do have dev/QA/release cycles with exactly -one- set of compiled binaries, using different configurations in each environment. There's no risk of having passwords/etc bundled in the binaries, and people can share their configurations to recreate issues that we're seeing.
I use an environment variable to point to a URL (which probably is a file:// URL) that has my configuration in it. This is very simple to setup and doesn't require the JNDI infrastructure.
Here's some sample code (typed from memory - I haven't compiled/tested this):
public void loadConfiguration() {
String configUrlStr = System.getenv("CONFIG_URL"); // You'd want to use a more
// Specific variable name.
if(configUrlStr == null || configUrlStr.equals("") {
// You would probably want better exception handling, too.
throw new RuntimeException("CONFIG_URL is not set in the environment.");
}
try {
URI uri = new URI(configUrlStr);
File configFile = new File(uri);
if(!configFile.exists()) {
throw new RuntimeException("CONFIG_URL points to non-existant file");
}
if(!configFile.canRead()) {
throw new RuntimeException("CONFIG_URL points to a file that cannot be read.");
}
this.readConfiguration(configFile);
} catch (URISyntaxException e) {
throw new RuntimeException("Malformed URL/URI in CONFIG_URL");
}
}
you can just store then is a normal java properties file that is on the class path and just load the properties?
it is straightforward and pretty simple.. unless I am missing something
My favorite places are : Environment Variables and Properties files (as suggested by Jared and kgiannakakis above.)
Database Table storing environment properties
However one other simpler solutions is to have Database table storing environment properties.
If your application uses database
- this is relatively easy to setup
- Gives really easy way to control/change values
- It can be integrated in the process well by making it part of DB scripts