Configuring J2EE Deployment Descriptors

Deployment descriptors are J2EE components that manage data connections when connecting to databases or maintaining unique transactions between web sessions.


June 30, 2006
URL:http://www.drdobbs.com/jvm/configuring-j2ee-deployment-descriptors/jvm/configuring-j2ee-deployment-descriptors/189401694

Steve is president and CTO of Catalyst Systems. He can be contacted at [email protected].


If you're a Java developer transitioning to J2EE, you'll encounter many new coding standards—and none are more important than learning to configure "deployment descriptors." Deployment descriptors are J2EE components that manage data connections when connecting to databases or maintaining unique transactions between web sessions.

Managing J2EE deployment descriptor files in large, enterprise environments can be tedious because deployment descriptors are unique to the web server to which they are deployed. When creating WARs and EARs from within an IDE, the IDE allows for the configuration of the deployment descriptor for a single web server. However, large enterprise environments often manage multiple web servers running in development, testing, and production. For each server, deployment descriptors must be updated to reflect the correct configuration for that server. Because IDEs do not handle the creation of multiple deployment descriptors for multiple configurations, the process must be handled manually, outside of the IDE. The result is an error-prone process that requires manual manipulation of the file for each server to which it is deployed. However, there are more efficient methods of managing deployment descriptors than the familiar brute-force method.

If you haven't started J2EE development, and don't understand why these files could cause such an immediate production headache, consider this: A deployment descriptor can be understood by comparing an EAR file to a ZIP file. Imagine that you have a ZIP file containing 1000 files. You want to copy that ZIP file to five different locations, but first you must change some of the text in 10 of the zipped-up files, and the changes are different for each location where you need a copy. This task requires recreating the ZIP file by uniquely configuring the 10 text files and rezipping them five times. Then you copy each ZIP file to the correct location according to the changes made. Essentially, this is the exact process required for deploying a J2EE application to an enterprise environment where multiple server configurations are required.

In most enterprise environments I have worked with, it is common to require a minimum of three different deployment descriptors for a single J2EE application. For example, your J2EE application connects to a database. The name of the database in development is "DEV-DB," in testing it is "QA-DB," and in production "PROD-DB." Your J2EE EAR or WAR needs to have three different versions of the deployment descriptor, one for each database name.

This is a simple example and in many cases the changes between the deployment descriptors go far beyond the name of the database. Regardless, even one change to the deployment descriptor requires that the EAR or WAR be recreated to support multiple servers. When this is the case, I like to know that the correct version of the EAR or WAR is being deployed to the correct server. This problem is not entirely the fault of the J2EE specifications, but largely the result of increased use of third-party modules and interconnectivity of enterprise applications and services. A single application running on a production server may require parameters that specify multiple database connections, log file locations, message queues, security information, CICS HASH(0x180be78), and other server-specific information. Put that same application on a second production server for a different business unit and all of these values could be different. It is not unusual to see applications where as many as 100 values might change. Take an application using parallel development that has five production servers and a few test and development servers and the problem is compounded.

In most J2EE development environments I've worked on, creating multiple deployment descriptors is manual and occurs during a final build or assembly process. The manual task requires that you create a reproducible process to update the runtime values in the deployment descriptors, after development has completed, for each server in the enterprise with its unique configuration, constructing an EAR or WAR file for each environment, and delivering each to the correct environment.

Unlike C or C++, where the runtime environment configuration information can be contained in a separate configuration file and deployed along with the executable, J2EE requires all deployment descriptors and other files be archived into the EAR or WAR file, just like my ZIP file example. It is important to keep in mind that this use of configuration files transfers the problem from being a "deployment" issue, such as the case with C or C++, and squarely into being a "build" issue. This is because an EAR or WAR file must be built for each uniquely configured server. In other environments (such as C or C++), the application team could build one executable and deploy it to multiple servers along with an initialization file (INI). With J2EE, a separate executable (the EAR or WAR file) must be built for each server so that the unique configuration contained in the deployment descriptors is archived into the EAR or WAR file.

Assembling Multiple EARs and WARs

Before you go too far down the path of J2EE development, look beyond what your IDE can provide in the assembly of multiple EARs or WARs. IDEs only allow the creation of one version at a time of your EAR or WAR. If you plan on staying within the IDE, this means you need to adopt a process of building your EAR or WAR multiple times—one for each configuration that your installation requires. The need for automating the process eventually becomes apparent as you quickly tire of this point-and-click assembly method.

To address this complexity, you need to define a solution to build and deploy the EARs or WARs that contain an application and the runtime-specific configuration parameters. This customization requires planning. If you choose to write an in-house solution, get ready for Perl and Ant/XML scripting, along with documentation and providing training to all developers who may be involved in the final assembly process. Also, you can expect to deal with the pains of trial-and-error before coming up with the correct solution that works for your organization. But all of this effort is worth it because the alternative—risking your production environment—is unthinkable for most developers. Here are some tips that will assist you with this process and keep you from the dead ends I've seen many J2EE development shops go down.

Tip #1: Watch Out for Parameterizing

A common tactic many J2EE developers use for addressing configuration issues involves parameters. This may seem practical at first, but you eventually see why it is wrong. When developers are asked to take a working application and parameterize properties and values in files, it means that if a developer has coded a particular property in a file for a log file location with a value of C:\appsdev\efoobar\comm.log, then the developer will create a parameterized version of the same file, where the value has a parameter such as @COMM_LOG_LOC@. The strategy then is to replace the parameter with values meaningful to the different target runtime environments; for example, C:\appsqa\efoobar\comm.log for a test server and C:\appsprod\efoobar\comm.log for a production server.

The reason that parameterizing lots of files is popular is that Ant Copy and Replace tasks have a nifty search-and-replace capability for tokens of this type, and it is tempting to make use of this tool in hand. However, additional labor is required to do the parameterization, and it is a risky manual process to eyeball and parameterize every relevant runtime value in a set of files. If one of these values is missed, the error would not be caught until runtime, after the EAR or WAR files have been built and deployed. I have found that at larger J2EE development sites, values that should be parameterized will inevitably occasionally get overlooked, causing a production "down" problem—all because of a small parameter. Your customers will not understand, and see the application as unstable.

In this case, the burden for developers is not trivial—you must now keep track not only of which properties need to be changed for each environment, but also which properties in which files have been parameterized. While there are situations where it may be necessary to go this route, parameterizing property values should be minimized and used for as small a subset of files and parameters as possible. In short, starting out parameterizing everything in sight is a common mistake to watch out for.

Tip #2: Use a Spreadsheet

Okay, using a spreadsheet sounds odd, but I've seen it serve as a lifesaver in more than one instance. Managing parameters is really about documenting and tracking the configuration of each server and the infrastructure. Providing this information in a spreadsheet does two things. First, it provides a central location for all of the configuration information that can be managed by different people. Second, the information from most spreadsheet programs can be exported in a format that can be used as part of an automated process. Microsoft Excel spreadsheets can be saved as either XML or comma-separated-variable (CSV) files. The XML file can be processed with XML Path Language (XPath) using the Ant XSLT task, and the CSV format lends itself more to processing by Perl. Table 1 lists typical spreadsheet column names, while Table 2 lists typical rows for managing runtime values.

Column Cell Content
Environment Identifies the type of environment such as development, testing, or production.
Instance Indicates a particular server when more than one may be in the same environment.
Archive The name of the JAR, WAR, or EAR file to be changed.
File The name of the file that contains values that change between environments and instances.
Name/XPath Locates and identifies the value to change.
Value Sets the parameter value that is to be used for the environment and instance indicated in that row.

Table 1: Columns for managing runtime values.

In large organizations, it's a good idea to use two spreadsheets—one for application-specific values determined by developers, and the other for runtime infrastructure values. Security, change control procedures, or simply the size of an organization may require that a production control team separate from developers maintain the infrastructure spreadsheet. A Microsoft Access-type database can also be used, but you lose the advantage of file-based version control. If you have properties files all over the place, run—don't walk—to your favorite spreadsheet tool.

Tip #3: Modify the Development Version of the EAR or WAR File

The final suggestion for assembling your EARs and WARs starts with the developer's version of the EAR or WAR along with an exported spreadsheet. You need to write a script that unJARs the EAR or WAR, taking out the files you need to modify, update them based on the spreadsheet data, and put them back by reassembling the EAR or WAR file.

You can extract individual files from the WAR or EAR files with a command-line JAR program or the Ant unJAR task. Remember, you can't extract a file inside of an archive that is inside another archive. For example, you can't directly extract a web.xml file inside a WAR file that is archived in an EAR file. First, you will extract all of the contents of the EAR file and then operate on each archive in turn. The ZIP file analogy was oversimplified: You are actually operating on ZIP files within ZIP files.

File types that often need to be updated for each environment include:

Listing One, update_deployment.pl, is Perl code that calls to the Ant XMLTask Replace and Insert tasks used to perform the updating of your deployment descriptors. Listing Two is a CSV input file that is similar to your spreadsheet export. Listing Three (available electronically; see "Resource Center," page 5) is a deployment descriptor that's updated by running update_deployment.pl. Listing Four (also available electronically) is the result of the deployment descriptor after executing the update_deployment.pl script. This example code does not include extracting or rearchiving the deployment descriptor files from the WAR or EAR. I've given you the hard code, extracting using JAR or unZIP is the easy step.


# This sample code shows how to use a CSV file to update deployment descriptor 
# configuration data. It generates an Ant build.xml file that uses the xmltask
# replace and xmltask insert to perform the updating of the configuration data
# within the deployment descriptors. 
#Usage: update_deployment.pl <CSV File> <Deployment Descriptor XML File>
#Author: Steve Taylor, Catalyst Systems Corporation 

################################
# Read the CSV file into a list
################################
open(FP,$ARGV[0]);
@lines = <FP>;
close(FP);

################################
# Remove Header Line
################################
shift @lines; 

#############################################################
# Initialize output list with xmltask input and output files
#############################################################
push @xmltask,"<xmltask source=\"$ARGV[1]\" dest=\"updated_$ARGV[1]\" 
                                                  preserveType=\"true\">\n";

#########################################
# Loop through the lines in the CSV file
#########################################
foreach $line (@lines)
{
 $line =~ s/\n//g;

 ###############################
 # Split the CVS into parts
 ###############################
 ($env,$instance,$archive,$file,$xpath,$value) = split(/,/,$line);
 
 ($action,@x) = split(/ /,$xpath);
 $xpath = join(" ",@x);
 
######################### ###############################
 # Write ANT Xml for xmltask insert based up on CSV data 
 ########################################################
 if ($action =~ /Insert/i)
 {
  push @xmltask, '       <insert  path="' . $xpath . '" 
                                                 position="after"' . ">\n";
  push @xmltask, '          <![CDATA[' . $value . "]]>\n";
  push @xmltask, "       </insert>\n";
 }
 
 #########################################################
 # Write ANT Xml for xmltask replace based up on CSV data 
 #########################################################
 if ($action =~ /Replace/i)
 {
  push @xmltask, '       <replace path="' . $xpath . '" 
                                            withText="' . $value . "\"/>\n";
 }
}

###############################
# Close xmltask tag 
###############################
push @xmltask, "     </xmltask>";

###############################
# Fill Out build.xml
###############################
$build_xml =<<ENDXML;

<project name="Deployment Update" default="update" basedir=".">
 <taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask"/>
   <target name="update">
     @xmltask
   </target>
</project>
ENDXML

#############################################
# Write out the actual build.xml to the file 
#############################################
open(FP,">build.xml");
print FP $build_xml;
close(FP);

###############################
# Run Ant
###############################
print `perl -S runant.pl 2>&1`;
Listing One


Env,Instance,Archive,File,Xpath,Value
Production,DB1,Minibank.war,application.xml,
    Replace /application/module/web/context-root/text(),banking/MinibankWEB
Production,DB1,Minibank.war,application.xml,
    Insert /application/module/web,<description>Production 
                                  context root for MinibankWEB</description>
Listing Three
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE 
  // Application 1.2//EN" "http://java.sun.com/j2ee/dtds/application_1_2.dtd">
<application id="Application_ID">
      <display-name>MinibankEAR</display-name>
      <description>Minibank Example Enterprise Application</description>
      <module id="EjbModule_1">
        <ejb>MinibankEJB.jar</ejb>
      </module>
      <module id="WebModule_1">
        <web>
            <web-uri>MinibankWEB.war</web-uri>
            <context-root>MinibankWEB</context-root>
</web>
      </module>
   </application>
Listing Four
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE 
  // Application 1.2//EN" "http://java.sun.com/j2ee/dtds/application_1_2.dtd">
<application id="Application_ID">
      <display-name>MinibankEAR</display-name>
      <description>Minibank Example Enterprise Application</description>
      <module id="EjbModule_1">
        <ejb>MinibankEJB.jar</ejb>
      </module>
      <module id="WebModule_1">
        <web>
            <web-uri>MinibankWEB.war</web-uri>
            <context-root>banking/MinibankWEB</context-root>
</web>
<description>Prodution context root for MinibankWEB</description>
      </module>
   </application>
Listing Two

Now that you've extracted and modified the files, the last step is to put them back in the EAR or WAR. Be extremely organized about which files are source files and which are the modified files. It is important to clearly separate files that are used as source and files that are modified for rearchiving. After rearchiving the new EAR or WAR file, you are ready to repeat the process for the next one.

Conclusion

Don't procrastinate when defining your process for configuring your deployment descriptors—it only causes more problems. If you follow the guidelines I've presented and review your server architecture early on, you will save yourself from deploying a perfectly good EAR or WAR to production, when they were configured for development. And remember, don't start parameterizing everything early on. This may look like the easiest road, but it becomes the hardest road as your application matures and moves to different environments. J2EE development will take you where you need to go. Just make sure you don't put up any road blocks that will prevent your J2EE application from all it can be.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.