WebSphere applications often rely on property files to contain environment-specific values. These files usually contain any settings that change between environments, and the files can be in a variety of formats Java properties (key/value pairs), XML, or a custom format. Many values can be kept the same throughout the environments, but values such as host names, user IDs and passwords, database schema names, and URLs usually change in each.
We see many of our customers struggling with the problem of how to manage the changes in application property files as the application gets promoted through a number of different environments, such as development, testing, and production. This article will outline some of the more common and proven solutions. While none of these solutions will always be the best for all situations, it is likely that one of the solutions presented here will fit your needs.
Roles and Responsibilities In a perfect world, groups within an organization would map nicely to the roles and responsibilities defined by J2EE. In reality, a disproportionate amount of the responsibilities fall within the realm of development. We find this especially true for property files where Operations' only responsibility may be to change a password, and development creates and maintains all of the remaining properties.
Previously many customers tended to manage all of the property files externally to the application, often deploying and updating these files by hand. Now it's more common for development teams to create property files for each environment and package them all into the EAR file building their applications to use a cue from each runtime environment to indicate which properties are to be used in that environment. We have found that roles and responsibilities generally have the largest impact on which of these solutions work best within an individual organization.
Options We have seen environment-specific properties managed in a number of ways; many of them are outlined in Table 1.
How to Load Them? At Build Time Using Ant There are two common strategies for replacing environment-specific information at build time; (1) replacing the entire file with files marked for each environment, or (2) replacing tokens in all property files from an environment-specific name/value pair. Both of these techniques can easily be done using Ant.
The following copy task shows how you can use Ant to replace all property files that end in a specific extension (such as ".dev", or ".stage") to another directory while removing the extension. The extension to replace is specified by the property ${env}, and could be passed into Ant when the build is invoked. The <mapper> element is the key to the copy command to perform the replacement.
This method is useful when your property files vary greatly between different environments, such as log4j.property files, which generally contain entirely different logging strategies and configurations in production than development.
<copy todir="${destdir}"
overwrite="true">
<fileset dir="${srcdir}"
includes="**/*.${env}"/>
<mapper type="glob"
from="*.${env}" to="*"/>
</copy>
You may also want to first copy all files except those ending in any of the environment-specific endings, so that any property files that don't require an environment-specific ending are picked up.
<copy todir="${destdir}">
<fileset dir="${srcdir}">
<exclude name="**/*.
dev"/>
<exclude name="**/*.
stage"/>
<exclude name="**/*.
prod"/>
</fileset>
</copy>
An alternate method is to replace tokens among all property files that would specify environment-specific values. This method is more useful if you have just a few substitutions to make in many different files. The <replace> task accepts a file (replacefilterfile) which contains a lists of tokens and the values to substitute. Again, if the ${env} property is set to "DEV" when invoking Ant, the following example would replace the tokens specified in the replace.properties.DEV in all of the .property and .xml files, such as "sample.properties". build.xml:
<replace dir="${build.dir}/properties"
replacefilterfile="replace.properties.${env}">
<include name="**/*.properties"/>
<include name="**/*.xml"/>
</replace>
replace.properties.DEV:
@@SERVER_NAME@@=mydevserver @@USER_NAME@@=devuser
sample.properties:
connect.url=@@SERVER_NAME@@ connect.username=@@USER_NAME@@
These two techniques could be used together if your application contains property files that fit into both categories. In this case, the file copy method should be used first to move the files into a build directory, and then the replace task applied to substitute tokens within those files.
The disadvantage of this process is that unique EAR files need to be built for each environment. This could conflict with your organization's policies on configuration management, which likely require the EAR file to be locked after successful testing. Alternatively, since some organizations build fresh for each environment from frozen source, this technique might work well.
At Runtime Using Resource Bundles Java provides a simple, out-of-the-box solution which can be exploited to easily load the correct files dynamically at runtime. By setting a "variant" on the WebSphere Application Server instance, the ResourceBundle class will load the appropriate property file with no additional configuration necessary short of naming your property files correctly. The ResourceBundle class should load the correct property file as long as the language and locale on your servers matches what you have named the file.
The variant is set by passing the "-Duser.variant" property on the JVM command line. This can be set on a server instance from the WebSphere admin console. The argument is added on the Application Server's -> <servername> -> Process Definition page under the field labeled Generic JVM arguments (see Figure 1).
Once set for a server, the following code can be used to load an environment specific property file.
ResourceBundle rb =
ResourceBundle.getBundle("myproperties");
String url =
rb.getString("connect.url");
On a server installed on a machine set to the use the English language and US locale, the ResourceBundle will first look for a file named:
myproperties_en_US_dev.properties
and will then move up the filename, until it will finally try to load a file called:
myproperties.properties
The downside to this approach is that it can be confusing to mix language/location strategies with environment determinations. It can also potentially conflict with third-party utilities that use the variant to further delineate the locale of the runtime environment.
At Runtime Using JNDI Resource Environment References JNDI can also be used to store environment-specific information, and can be referenced by your application through local references just as EJBs or DataSources are. These bindings can be used to provide specific string values, or URL references to property files containing environment-specific information located on the server (see Figure 2).
These references are referred to as Resource Environment References, and are specified with the <resource-env-ref> tag in the Web or EJB deployment descriptors.
If you wish to use these references with the WSAD test environment, a WebSphere binding must be specified in the Deployment Descriptor wizard, and the appropriate binding defined in your local test server.
Unfortunately, there is no place in the WSAD Test Server Configuration wizard to set these references. You must use the administration console to create the namespace bindings for both WAS and the WSAD test environment. The binding can be set from Environment >Naming >Name Space Bindings page (see Figure 3).
Be aware of the scope on which you set the environment references. Although you may want to set a reference on a cell basis, to cover all the machines in a cluster you will need to point the binding to the correct place (namely, "cells/persistent/<x>"), whereas server-scoped namespace bindings will appear right in the root.
Looking up the references is no differen t than looking up any local environment reference:
Context ctx = new
InitialContext();
String propFile = (String)ctx.
lookup("java:comp/env/url/
properties");
If you want to comply with the J2EE specifications, once you have obtained the reference it should be read via the java.net.URL class instead using direct file IO.
URL propFileURL = new
URL(propFile);
InputStream in = propFileURL.open
Stream();
At Runtime Using a Custom Property Manager Writing your own custom code to load environment-specific properties has some advantages, such as the ability to include logging or dynamic reloading on file change, but there are a few things to keep in mind if you roll your own:
Beware of classpath isolations. Chances are you are going to want to read in those property files off the classpath, and not directly off the filesystem. Because of the way WebSphere 5.x uses multiple classloaders for different parts of your application, it is important that you use the correct classloader for the job. If you place this property manager code in a utility JAR, you probably don't want to use the classloader that loaded the property manager. Instead, you would want to use the classloader of the component that called the property manager. A good way to do this is to use the context classloader:
Thread.currentThread().getCon
textClassLoader().getResour
ceAsStream();
WebSphere ensures that the context classloader is set appropriately before each call into your application.
Consider building hierarchical property file searches (like ResourceBundle). Instead of just appending a single environment token on to the end of any property file searches, consider building your search to navigate up a sequence of extensions, ending with no extensions. This will allow developers to scope their property files as necessary. A simple example would be if your custom property manager is asked to load "myprops.properties" while running in the production environment, it first looks for "myprops.properties.prod" and then "myprops.properties." In a clustered environment, you may want to indicate a specific server in a cluster, along with the environment type itself, such as "myprops.properties.prod.pdserver1."
Where to Put Them A common problem in J2EE applications is deciding on where to place properties files. There are three main options, which will be discussed briefly here. Once one of these options is chosen, then an appropriate access method will need to be selected (see Table 2).
External to the EAR File Most early J2EE applications accessed property files that were external to the EAR. This was mainly due to ease of implementation.
The major disadvantage of having any application resources external to the EAR is that there will be additional packaging and deployment steps since all assets are not a part of the EAR.
On the other hand, in environments where environment-specific values are closely managed and guarded by Operations this approach could be beneficial. The development team would define what the property file should look like, as well as how it should be accessed (e.g., direct file system access or via classpath), and the Operations group would create and place the file accordingly.
This method works best with the JNDI Resource References, as the administrators can bind the location of the files at deployment time while the application code can utilize J2EE compliant loading by using the URL class instead of direct java.io package loading.
Using a Properties JAR File If you are not worried about easily editing the property files on the WebSphere servers themselves after deployment, the easiest way to bundle property files on the classpath is to create a properties JAR file and add it to your application's EAR.
An important step to remember is to add the property JAR to the manifest classpath of any component that will be accessing the properties.
This approach accomplishes two common major goals of having the EAR self-contained and being a fully J2EE-compliant implementation.
Properties Files in Root of the EAR Some WebSphere shops have the requirement that properties be packaged in the EAR, yet also be accessible without having to extract them from a JAR. They do this to allow for easy checking or changing of the properties. This solution relies on the classloader's ability to search through non-JAR resources in the EAR.
This solution could be considered "not fully J2EE compliant." It relies on a feature that is recommended, but not mandated by J2EE. The following excerpt from section 8.1.1.2 of the J2EE 1.3 specification explains this recommendation:
J2EE products need not support this mechanism for reference to classes or resources that are not in .jar files included in the .ear file. However, support for such usage is encouraged. Applications are encouraged to make use of such support only when necessary and where available, although currently such usage is non-portable.
WebSphere has implemented this functionality since version 4.0.x, however, since J2EE has not mandated it, IBM could withdraw this functionality at any time.
- Create a directory named "properties" in the root of the EAR project. This directory will be even with the META-INF directory.
- Place all properties files in this "properties" directory.
- Write code to access the properties file. This would normally be done with
Thread.currentThread().getContext
ClassLoader().getResourceAs
Stream(propFileName);
This code should normally be placed in a utility JAR that can be referenced by all other modules of the enterprise application.
- For each module that will access the properties file, make the following changes to the MANIFEST.MF file:
Manifest-Version: 1.0
Class-Path:../<NameOfEarProject/
properties properties
WSAD will show errors on the EAR project due to the entries in the manifest, but the errors can be ignored. There are two entries in the manifest file because WSAD loads code from the workspace differently than WebSphere Application Server loads it at runtime.
The "../<NameOfEarProject/properties" entry is for execution within the WSAD test environment. If you not using the WSAD test environment, this entry in the manifest can be excluded. This tells the classloader to look up one directory (to the workspace root) and in the <NameOfEarProject> directory (the folder for the EAR project). This has to be done because WSAD treats all projects/modules as being at the same level in the hierarchy. In other words, WSAD does not treat the modules and JARs as though the EAR project contains them.
The "properties" entry is for runtime in WebSphere Application Server. In WAS, the EAR is the top level with respect to the modules' classpaths, and each module is one level deeper. The modules are extracted to the disk in the same hierarchy in which they are stored within the EAR file.
There should be no adverse effect of having both entries in the classpath permanently. A conflict could occur in WSAD if there were a "properties" directory in the module itself (unlikely). A conflict in WebSphere Application Server would occur if "../<NameOfEarProject/etc" existed relative to the installed location of the EAR (extremely unlikely). Either of these scenarios would only occur if it were created intentionally.
As you may have observed, problems may occur when reusing the component with the reference to the properties directory. In WSAD, the manifest will reference a directory name that contains the name of the EAR project. This will be a problem when the module needs to be reused in multiple EARs.
Pick Your Location Carefully Not all property file locations will work with all methods to load properties. Table 3 indicates which methods can work together.
As you can see, there is a variety of approaches for managing environment-specific properties. Most of the companies that use WebSphere are using one of the options mentioned in this article, or a slight variation of one of them. It is nearly impossible to say that one method is always desirable over the others; hopefully this article presented the information in such a way that you can choose the approach that works best for your organization.
|