Section 17.5. Enterprise Tasks


17.5. Enterprise Tasks

In addition to the basic Java development tasks we've discussed in the previous sections, enterprise application development requires special support in terms of managing component development and testing; assembling and deploying applications; preserving platform portability in the face of different application server vendors; and managing multiple application environments.

In the following sections, we'll explore each of these areas in terms of the Ant tasks that are relevant to each and look at some approaches to designing your Ant buildfiles to support each of these enterprise development issues.

17.5.1. Assembling Components

When developing J2EE components, you need to perform many of the basic Java development tasks we've already seen: compile Java code into classes, construct a jar file from a set of files, move the jar file from one place to another, and so forth. Some complexities are introduced when working with enterprise components, however. Component archives need to be assembled using specific file layouts. Depending on the application server being used, additional processing of your Java classes or component archives may be necessary as well.

The jar file formats used for component assemblies are more complicated than the simple class hierarchies used for libraries. As discussed in Chapter 2 and detailed in Appendix A, component archives follow a specific structure that dictates where each asset (deployment descriptors, libraries, classes, etc.) must reside in the archive structure. You could use the basic tasks described in the earlier sections to construct component archives, of course. You could use the mkdir and copy tasks to construct the required archive directory structure in a temporary location and then use the jar task to construct the actual component archive file. But this would require you to correctly interpret the component archive format and map that into your buildfile yourself (e.g., libraries in a web archive go into the WEB-INF/lib directory, compiled classes go into the WEB-INF/classes directory, etc.). Luckily, Ant provides tasks that create J2EE component archives directly, allowing you to simply specify the required elements of the archive. The Ant tasks assemble these assets into the standard archive structure for you and provide some other useful shortcuts as well.

17.5.1.1. Web components

The core of Ant's support for assembling web component archives is the war task. This task allows you to create a web archive by specifying the elements that should be included using task attributes and subelements in the task declaration. The attributes on the task element specify the name of the war file to be created and the deployment descriptor for the module. The task supports a set of child <fileset> elements that allows you to specify the HTML, JSP, and other web assets to be put into the archive's root directory; the files (other than the deployment descriptor) to be put into the WEB-INF directory; the Java classes and other classpath-accessible files to be put into the WEB-INF/classes directory; and the full libraries to be put into the WEB-INF/lib directory.

In our example buildfile, we already have an archive target that creates a simple jar file from the contents of the project's classes directory. In Example 17-5, we show a new create-wars target that uses the contents of the classes, config, and web directories in the project to construct a web component archive. To support this target, we need two new properties in our build.properties file to specify the location of the configuration and web content directories:

 . . . # Location of application configuration files config.dir = config   # Location of all web-related content (HTML files, etc.) web.dir = web 

This maintains our pattern of keeping all project configuration details in one place and referencing these properties where needed in the buildfile.

Example 17-5. Target for assembly of web archives
 . . .     <!-- Create the web archives needed for this application -->     <target name="create-wars"             description="Create the web archives needed for this application"             depends="compile">         <!-- Delete any existing war files -->         <delete>            <fileset dir=".">               <include name="*.war"/>            </fileset>         </delete>         <!-- Define a pattern for the set of files within the web dir              that should be included in the root of the archive. -->         <patternset >             <include name="**"/>         </patternset>         <!-- Define a pattern for the set of classes from the classes              dir that should be included in the archive -->         <patternset >             <include name="**"/>         </patternset>         <!-- Define a set of libraries to be included in the archive -->         <patternset >             <include name="xercesImpl.jar"/>             <include name="xml-apis.jar"/>             <include name="xmlParserAPIs.jar"/>         </patternset>         <!-- Invoke the war task -->         <war warfile="${proj.name}.war"              webxml="${config.dir}/web.xml">             <!-- Put any files specified by the war.files patternset in the root                  of the web archive. -->             <fileset dir="${web.dir}">                 <patternset ref/>             </fileset>             <!-- Include any classes specified by the war.classes patternset -->             <classes dir="${java.classes.dir}">                 <patternset ref/>             </classes>             <!-- Include any libraries specified by the war.libs patternset -->             <lib dir="${lib.dir}/xerces-2_6_2">                 <patternset ref/>             </lib>         </war>     </target> . . . 

We've made the create-wars target depend on the compile target to ensure that all of the project code is compiled before we try to assemble our web module(s). In the target itself, we first clear out any war files left over from previous runs of this target using the delete task. Since we intend this target to be the creator of all web archives for the project, we can safely delete all of the files with the war suffix. Once this is done, we define a few <patternset> elements that specify the set of general web assets and Java classes to be included in the archive. Here we're simply including the entire contents of their respective directories in the <patternset> since we have all of our web content and classes in singular directories, and we want them included in their entirety. But if that changes and we need to filter elements from each directory or we need to include contents from multiple subdirectories in the project, we simply need to adjust the <patternset> elements to suit the new project file structure.

The invocation of the war task itself is fairly straightforward. We specify the name for the war file using the warfile attribute on the task element using the proj.name property as the base for the filename. We also specify the deployment descriptor for the archive in the attributes, setting the webxml attribute to the web.xml file in our config directory. We then use child elements of the war task to specify files to be included in the root of the archive as well as Java classes that should be included in the archive. We use the <patternset> elements defined earlier in the target to define the files to be included in each case.

17.5.1.2. EJB components

Assembling EJB component archives is slightly more complicated than assembling web archives, due to the application server-specific details that are often required to deploy an EJB module. As discussed in Chapter 2, these can include vendor-specific deployment files or the need to generate vendor-specific support classes for the EJB components in your module.

Ant provides support for creating EJB modules from project assets through the ejbjar task, which is included in its bundled set of optional tasks. The task searches recursively through the directory specified by its descriptordir attribute, looking for relevant EJB deployment descriptors. For each EJB deployment descriptor that it finds, ejbjar builds an EJB archive by parsing the EJB deployment descriptor, determining the classes that are required for the EJB components. These classes are copied from the directory specified in the srcdir attribute. Depending on how the ejbjar task is configured, it can also locate vendor-specific configuration files in the descriptordir directory and include those in their appropriate locations in the archive. The ejbjar task can also generate vendor-specific support classes, if configured to do so. Once this is all accomplished, the task writes out a full EJB module archive to the location specified by the destdir attribute.

As opposed to the war task we saw in the previous section, the ejbjar task is categorized as an optional task in Ant because it requires external libraries to operate. In the case of ejbjar, the task requires the Jakarta Byte Code Engineering Library (BCEL) in your classpath when you run Ant since it uses this library to calculate class dependencies for your EJB components as it determines which classes need to be included in your EJB archive. The BCEL can be downloaded from the Apache Jakarta project site (http://jakarta.apache.org/bcel). In order to use the ejbjar task, either you'll need to have this library on your classpath when you invoke Ant or you'll have to copy the library into the Ant lib directory.

The ejbjar task can be used either to create generic EJB archives, containing only the standard EJB deployment descriptor and your classes or to generate EJB modules targeted for a specific application server. To start, let's define a target that simply generates a generic EJB module from your project assets, shown in Example 17-6. The create-ejbjars target simply invokes the ejbjar task, indicating with the task attributes that deployment descriptors will be found in the project configuration directory (using the descriptordir attribute), that Java classes for the components are to be found in the project's classes directory (using the srcdir attribute), and that the generated EJB module file should be written in the project's base directory (using the destdir attribute). The other attributes we're using on the ejbjar task specify how the task should generate the module archive, in terms of the name of the jar file (naming), how to calculate dependent classes that need to be included in the archive (dependency), and so forth.

Example 17-6. Target for generation of EJB modules
 . . . <!-- Create any ejb modules required for this application --> <target name="create-ejbjars"         description="Generates an ejb-jar archive for this application"         depends="compile">     <!-- Invoke the ejbjar task, looking for deployment descriptors          in the project config directory.  We are using "directory"-based          naming here, with the name of the directory containing the EJB          deployment descriptor used as the basis for the ejb-jar filename. -->     <ejbjar descriptordir="${config.dir}"             srcdir="${java.classes.dir}"             destdir="${basedir}"             naming="directory"             genericjarsuffix=".jar"             flatdestdir="true"             dependency="super">         <!-- Use local copies of descriptor DTDs, in case we're not online -->         <dtd publicid=              "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"              location="${lib.dir}/j2ee/dtd/ejb-jar_2_0.dtd"/>         <!-- Parse only EJB descriptors -->         <include name="**/*ejb-jar.xml"/>     </ejbjar> </target> . . . 

We're using preexisting project properties, java.classes.dir and config.dir, to indicate where the ejbjar task should find the assets for the EJB module. But in order for the task to work as intended, we have to put our EJB deployment descriptors into subdirectories of our project's configuration directory. We've specified that ejbjar should use "directory" naming for the modules it generates, so the name of the subdirectory should be the name we want for the EJB module file. The name of this file is important, since application deployment descriptors need to reference the file once it's included in an application archive. If we had EJB components that we wanted assembled into an archive named ProfileEJB, for example, and assuming that our config.dir property is set to config, our configuration directory structure would need to look like Figure 17-1.

Figure 17-1. Sample directory structure for ejbjar task


We've also included a few subelements in our invocation of the ejbjar task. We added a <dtd> element, specifying a local copy of the EJB deployment descriptor DTD. This is helpful when you're developing offline; for example, as I write this I'm sitting on an airplane where I have little chance of making a URL connection to the Sun site where the original copies of the DTD lives.[1] Using local DTDs can also speed up the parsing process that ejbjar performs since it avoids a network lookup.

[1] Of course, this is helpful to me only because I'm paranoid enough to have copies of all the J2EE schemas and DTDs on my laptop.

The other child element is an <include> element that limits the scope of the ejbjar task to files in our configuration directory that end with ejb-jar.xml. This allows us to put other types of deployment descriptors and configuration files in our configuration directory, without risking a lot of confusion on the part of ejbjar.

As we already mentioned, this version of our target generates only a generic, standard EJB module archive. If the EJB container in your application server has additional requirements, such as vendor-specific deployment descriptors and support classes, you'll need to enhance this target to handle these additional server-specific requirements. The ejbjar task supports a number of common application servers with subelements that can be used to manage vendor-specific details. For example, if we knew we were going to deploy our EJB components to a JBoss EJB container, we could use the JBoss-specific subelement of ejbjar, <jboss>, to create an EJB module that can be deployed directly to a JBoss EJB container. The JBoss-enabled version of our create-ejbjars target is shown in Example 17-7.

Example 17-7. JBoss-enabled version of the EJB module target
 . . . <!-- Create any ejb modules required for this application --> <target name="create-ejbjars-jboss"         description="Generates an ejb-jar archive for this application"         depends="compile">     <!-- Invoke the ejbjar task, looking for deployment descriptors          in the project config directory.  We are using "directory"-based          naming here, where the name of the directory containing the EJB          deployment descriptor is used as the basis for the ejb-jar filename. -->     <ejbjar descriptordir="${config.dir}"             srcdir="${java.classes.dir}"             destdir="${basedir}"             naming="directory"             genericjarsuffix="-generic.jar"             flatdestdir="true"             dependency="super">         <jboss destdir="${basedir}"/>         <!-- Use local copies of descriptor DTDs, in case we're not online -->         <dtd publicid=              "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"              location="${jboss.home}/docs/dtd/ejb-jar_2_0.dtd"/>         <!-- Parse only EJB descriptors -->         <include name="**/*ejb-jar.xml"/>     </ejbjar> </target> . . . 

The inclusion of the <jboss> child element causes the ejbjar task to look for any JBoss EJB deployment files (named jboss.xml) wherever it finds a standard EJB deployment descriptor and to include the JBoss descriptor in the EJB module when it's assembled. Since JBoss automatically generates any required support classes when the module is deployed, there's nothing else that the ejbjar task needs to do to make the EJB module deployable in JBoss. But other application servers require you to pregenerate the support classes for the EJB modules and include them in the module archive when it's deployed. These tasks can be performed by ejbjar for the application servers that it supports. For EJB containers not supported by ejbjar, you'll need to handle this in other ways. Some EJB containers provide custom Ant tasks that you can import and use for processing EJB archives for deployment, for example.

It's important to note that putting environment-specific details into your buildfiles limits their portability. Our create-ejbjars-jboss target, for example, will work only in environments using JBoss application servers. If I give my project code to you and you want to deploy it in a WebLogic server, you would need to edit my buildfile to make it work in your environment. As much as possible, you want to avoid this situation so that the buildfile requires only configuration changes (e.g., to the build.properties file or any other configuration files that the buildfile references). Ideally, you want to be in a situation in which other developers can simply replace the default configuration files with ones that suit their environment, and the rest of the buildfile works as advertised.

"Creating Portable Build Processes" later in this chapter discusses some approaches to making your buildfiles as portable as possible, especially with regard to enterprise development tasks.

17.5.2. Assembling Applications

Creating J2EE application assemblies in Ant is very straightforward through the use of the built-in ear task. The ear task is a specialized version of the jar task, so it accepts all of the same child elements as the jar task. But the ear task automatically creates a jar file with the format of a J2EE application archive.

Example 17-8 shows a create-ear target that uses our project properties and the Ant ear task to construct an application archive. We've made the create-ear target depend on the create-wars and create-ejbjars targets to ensure that our component archives are assembled before we assemble our application. The ear task has a number of attributes and child elements that can be used to control its behavior. In our example, we're using the destfile attribute to put the ear file in the root of the project directory, using the project name as the base of the filename. The deployment descriptor is set using the appxml attribute, and we're using the application.xml file from the configuration directory. Finally, we're including any war and jar files found in the root of the project directory in the application archive using the includes attribute, assuming that they are all web or EJB component archives built using the create-wars or create-ejbjars targets. If we wanted to be more selective (e.g., we may not want some jar files included in the application archive), we could use specific archive filenames in the includes attribute.

Example 17-8. Target for creating application archives
 . . . <!-- Create an application assembly from our web and EJB components --> <target name="create-app"         description="Make an application assembly from the component archives"         depends="create-wars, create-ejbjars">     <ear destfile="${proj.name}.ear"          appxml="${config.dir}/application.xml"          includes="*.war, *.jar">     </ear> </target> . . . 

It's important to note that any war or ejb-jar modules created by the create-wars or create-ejbjars targets must be referenced in the application.xml descriptor using the same filenames created by those targets. Unlike the ejbjar task in Ant, the ear task does not automatically parse the application deployment descriptor and search for the files that must be includedit just pulls in any files referenced by the combination of the includes and excludes attributes on the ear task. If our project's proj.name property is set to ShoppingCart, for example, the create-wars target in Example 17-8 will create a war file named ShoppingCart.war. If we also have an EJB deployment descriptor in a CCardAccount subdirectory of our configuration directory, the create-ejbjars target will create an EJB jar file named CCardAccount.jar. Since our create-app target is picking up both of these files as-is, the application.xml file must reference them like so:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE application PUBLIC           '-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN'           'http://java.sun.com/dtd/application_1_3.dtd'> <application>     <display-name>JEnt Ant sample application</display-name>     <!-- Declare the web component module -->     <module>         <web>             <web-uri>ShoppingCart.war</web-uri>             <!-- Give the web components a URL root -->             <context-root>cart</context-root>         </web>     </module>     <!-- Declare the EJB module -->     <module>         <ejb>CCardAccount.jar</ejb>     </module> </application> 

As we saw with assembly of component archives, you may need to consider server-specific details when assembling application archives. Some application servers make use of their own application-level configuration files for various details not covered by the standard J2EE deployment descriptor, for example. If you need to include server-specific files in the ear file, you have to specify them explicitly using either the includes or excludes attribute or using child <fileset>, <include>, <exclude>, and <patternset> elements.

17.5.3. Deploying Components and Applications

Once you've compiled your code and assembled some component modules or an entire application archive, the next logical step is to deploy your code to an application server of some kind. As discussed in Chapter 2, the specific steps that need to be taken to deploy components and applications to an application server are server-specificit's done differently in Tomcat, JBoss, WebLogic, and WebSphere, for example. For some servers, like Tomcat and JBoss, deploying code is simply a matter of placing the archive file in a deployment directory that the server is configured to watch for new deployable code. In these cases, writing a target to deploy code is simply a matter of using the Ant file-oriented tasks to move or copy the generated archive file to the right place. Example 17-9 shows a target that deploys our application archive to a JBoss server. We've assumed here that a property named jboss.home has been set in the build.properties file to point to the root of the JBoss server installation. In the target, we just copy the application archive file (the target depends on the create-app target, so we can count on the archive existing) to the default deployment directory in JBoss, server/default/deploy.

Example 17-9. Target for deploying an application to JBoss
 . . . <!-- Deploy our application to a JBoss server --> <target name="deploy-app-jboss"         description="Deploy the application archive to a JBoss server"         depends="create-app">     <copy file="${proj.name}.ear"           todir="${jboss.home}/server/default/deploy"/> </target> . . . 

Other vendors require you to use bundled deployment tools to deploy code to their application servers. BEA WebLogic, for example, provides a deployer tool that you use to deploy code to running WebLogic servers. One form of the tool is a Java class named weblogic.Deployer that can be invoked from the command line to deploy either a component or application archive. You could use this Java utility to create a WebLogic version of our deploy target, using the generic java task, as shown in Example 17-10.

Example 17-10. Target for deploying an application to WebLogic
 . . . <!-- Define a classpath that includes the WebLogic library --> <path >     <path ref/>     <pathelement path="${weblogic.home}/server/lib/weblogic.jar"/> </path> <!-- Deploy the application to a WebLogic server --> <target name="deploy-app-weblogic"         description="Deploy the application archive to a WebLogic server"         depends="create-app">     <java classname="weblogic.Deployer"           classpathref="weblogic.deploy.classpath">         <arg line="-adminurl http://${weblogic.http.host}:${weblogic.http.port}"         />         <arg line="-user ${weblogic.user}"/>         <arg line="-password ${weblogic.password}"/>         <arg line="-name ${proj.name}"/>         <arg line="-redeploy ${proj.name}.ear"/>     </java> </target> . . . 

In the target, we first set up a classpath to be used to run the WebLogic utility, which includes our compilation classpath with the necessary WebLogic libraries appended to it. Then we use the java task to run the utility, using the appropriate arguments required by the utility class. Note that we also need to define a few more new properties in our build.properties file to support this target, to indicate the details about the WebLogic server being used when we run the target:

 # WebLogic-specific properties weblogic.home=/usr/users/jfarley/servers/dev-81 weblogic.http.host=localhost weblogic.http.port=8001 weblogic.user=system weblogic.password=password 

Some application servers provide custom Ant tasks that can be imported into your buildfile and used to drive the deployment process. WebLogic provides such an Ant task. Importing the custom task is done using the built-in taskdef task, specifying the appropriate classpath to locate the WebLogic classes:

 . . . <!-- Load the WebLogic Ant tasks from the server libraries --> <taskdef name="wldeploy"          classname="weblogic.ant.taskdefs.management.WLDeploy">     <classpath>         <path ref/>     </classpath> </taskdef> . . . 

Since the support classes for the Ant task are located in the same libraries as the weblogic.Deploy utility, we're just reusing the weblogic.deploy.classpath path that we defined earlier. Once the task is loaded, we can use it in our buildfile according to the server documentation. A simple example, roughly equivalent to the previous use of the weblogic.Deployer utility, is shown here:

 . . . <!-- Deploy the application to a WebLogic server --> <target name="deploy-app-weblogic-ant"         description="Deploy the application archive to a WebLogic server"         depends="create-app">     <!-- Invoke the WebLogic wldeploy task to deploy our application -->     <wldeploy action="deploy"               source="${proj.name}.ear"               name="${proj.name}"               user="${weblogic.user}"               password="${weblogic.password}"               adminurl="${weblogic.admin.url}"/> </target> . . . 

If we wanted our buildfile to be used in additional application server environments (such as Apache Geronimo or WebSphere), we'd need to write new versions of our application deployment target and potentially add new server-specific properties to our properties file.

To some degree, we can try to address this portability issue using the Ant built-in task, serverdeploy. As of Ant 1.6, this task directly supports WebLogic and JOnAS application servers (through server-specific child elements that invoke the corresponding deployment tools provided by these servers). The task also includes a generic child element that can be used to invoke other server deployment tools, assuming that they are provided in the form of a Java class with a main method and that all of the arguments can be passed into the utility using the command line. The serverdeploy task isn't sufficient to cover all possible application servers, however (there's no way to use it to deploy an application to JBoss, for example).

A more general-purpose approach to the application server portability issue is described next.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net