ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer

by Colin (Chun) Lu
11/16/2007

Introduction

Today's Java EE application deployment is a common task, but not an easy job. If you have ever been involved in deploying a Java EE application to a large enterprise environment, no doubt you have faced a number of challenges before you click the deploy button. For instance, you have to figure out how to configure JMS, data sources, database schemas, data migrations, third-party products like Documentum for web publishing, dependencies between components and their deployment order, and so on. Although most of today's application servers support application deployment through their administrative interfaces, the deployment task is still far from being a one-button action.

In the first few sections of this article, I will discuss some of the challenges of Java EE deployment. Then I will introduce an intelligent rule-based auto-deployer application, and explain how it can significantly reduce the complexity of Java EE system deployment. I will also give a comprehensive example on how to build XML rules using XStream utility library, how to extend and analyze the standard Java EE packaging (EAR), and perform a complex deployment task just by pushing one button.

Challenge 1: Package Limitations

A Java EE application is packaged as an enterprise application archive file (EAR). Java EE specification defines the format of an EAR file as depicted in Figure 1.

Figure 1
Figure 1. Standard Java EE EAR file structure

A standard EAR file meets the basic requirements for packaging an application as most of web-based JAVA EE applications are composed solely of web and/or EJB applications. However, it lacks the capability of packaging advanced JAVA EE application modules. For example, the following modules are often used in a JAVA EE application deployment, but cannot be declared in a standard EAR file:

Most of Java EE applications require Data sources, schema changes, data migrations, and JMS configurations. Today, these components have to be manually configured and deployed via an administration interface provided by the implementation vendor. This is typically the responsibility of the system administrator.

Challenge 2: Deployment Order and Dependencies

Another challenge to an application deployer is that he has to know the deployment dependencies and follow the exact order to deploy multiple deployments for one application.

A large Java EE application may have complex dependencies on other deployments. For example, corresponding database tables must be created before an application can be deployed; a JDBC data source must be configured ahead of a JMS server. In these situations, the deployer first has to coordinate with the application architect and developers to find out the deployment requirements and dependencies, and then make a detailed deployment plan. This process is not very efficient; we need a better solution.

Solution

How can we help a deployer survive these challenges? Is there a way to simplify this complex deployment process? A possible solution is to use the vendor proprietary capability to extend your EAR to be more intelligent. For example, WebLogic Server supports packaging JDBC and JMS modules into an EAR file, and the WebLogic Deployer can deploy your application as well as application-scope JDBC and JMS modules in one action. Isn't that useful? Wait a second, there are still limitations:

A practical solution is to make an intelligent XML rule-based auto-deployer by extending the Java EE packaging.

A Rule-Based Auto-Deployer

This solution has three main parts:

The suggested deployment work flow is illustrated in Figure 2.

Figure 2
Figure 2. Deployment work flow

Case Study

Let's think about the deployment of a Service Order Processing application to a WebLogic server. Here are the deployment tasks that need to be done:

1. Deployment Tool: XML Rule Generator using XStream

The first step is to generate an XML rule from a plan by the application assembler.

Step 1: Define a deployment plan

To define a deployment plan, the application assembler discusses the deployment requirements with developers and architects. For the sample service order processing system, a deployment plan is defined below:

DataSource,t3://localhost:7001,NONXA,jdbc/testDS,colin,password,jdbc:oracle:thin:@localhost:1521:localdb,oracle.jdbc.driver.OracleDriver
SQL,t3://localhost:7001,jdbc/testDS,sql/testDS.sql
JMS,t3://localhost:7001,PTP,testJmsServer,testJmsRes,jmsTestConnFactory,jms/conn,testQueue,jms/testQueue
LDAP,ldapread-server.com,489,cn=one_button_deployment,o=system_configuration,ldif/test.ldif
APPLICATION,t3://localhost:7001,SOManager,Release v1.0
Step 2: Use the Deployment Tool to generate an XML document from the plan

After the plan is defined, the application assembler runs the deployment tool application to feed in the plan and generate the XML rule document.

The sample application is shown Figure 3.

Figure 3
Figure 3. Sample deployment tool

And the generated XML document of the deployment rule (rule.xml) looks like this:

<?xml version="1.0"?> 
<rule>
  <modules>
    <datasource>
      <targetUrl>t3://localhost:7001</targetUrl>
      <xaType>NONXA</xaType>
      <jndiName>jdbc/testDS</jndiName>
      <user>colin</user>
      <passWord>ARdHTJSytteIAE</passWord>
      <dbUrl>jdbc:oracle:thin:@localhost:1521:localdb</dbUrl>
      <driver>oracle.jdbc.driver.OracleDriver</driver>
    </datasource>
    <sql>
      <targetUrl>t3://localhost:7001</targetUrl>
      <jndiName>jdbc/testDS</jndiName>
      <sqlStatement>sql/testDS.sql</sqlStatement>
    </sql>
    <jms>
      <targetUrl>t3://localhost:7001</targetUrl>
      <destType>PTP</destType>
      <serverName>testJmsServer</serverName>
      <resName>testJmsRes</resName>
      <connFactory>jmsTestConnFactory</connFactory>
      <facJndiName>jms/conn</facJndiName>
      <destName>testQueue</destName>
      <destJndiName>jms/testQueue</destJndiName>
    </jms>
    <ldap>
      <host>ldapread-server.com</host>
      <port>489</port>
      <basedn>cn=one_button_deployment,o=system_configuration</basedn>
      <ldif>ldif/test.ldif</ldif>
    </ldap>
    <application>
      <targetUrl>t3://localhost:7001</targetUrl>
      <appName>SOManager</appName>
      <version>Release v1.0</version>
    </application>
  </modules>
</rule>
Design of the Deployment Tool

For this sample tool, a Factory design pattern is applied to build deployment module, POJOs, from a deployment rule, after that these POJOs are serialized to XML documents by a RuleGenerator using XStream utility library. The class design is shown in Figure 4.

Figure 4
Figure 4. Class diagram of deployment tool

As shown in Figure 5, the RuleGenerator reads a DeployPlan as a file or input stream and then parses the plan to generate a DeployRule Java object. The DeployRule Java object will be serialized to XML document using XStream utility library.

Figure 5
Figure 5. Sequence diagram of deployment rule generation

The sample code of RuleGenerator is shown below:

package onebuttondeploy.tool;

import java.io.FileWriter;
import java.util.ArrayList;

import onebuttondeploy.Constants;
import onebuttondeploy.rule.ApplicationModuleVO;
import onebuttondeploy.rule.DatasourceModuleVO;
import onebuttondeploy.rule.DeployModule;
import onebuttondeploy.rule.DeployRule;
import onebuttondeploy.rule.DeploymentModuleFactory;
import onebuttondeploy.rule.JmsModuleVO;
import onebuttondeploy.rule.LdapModuleVO;
import onebuttondeploy.rule.SqlModuleVO;

import com.thoughtworks.xstream.XStream;

/**
* @author Colin (Chun) Lu
* @Email colinlucs@gmail.com
*/


public class RuleGenerator {
    /*
     * Convert deploy plan to XML document
     */
    public static String getXmlRule(String plan) throws Exception{
        return generateDeployRuleXml(plan2Rule(plan));
    }
    
    /*
     * Instantiate DeployRule object from deploy plan
     */
    private static DeployRule plan2Rule(String plan) throws Exception{
        if (plan == null)
            return null;
        DeployRule rule = new DeployRule();
        String[] deployModules = plan.split("\n");
        for (int i = 0; i < deployModules.length; i++) {
            DeployModule module = 
            DeploymentModuleFactory.getDeploymenModule(deployModules[i]);
            if (module != null)
            rule.addModules(module);
        }
        return rule;
    }
    
    /*
     * Serialize DeployRule object to XML document usign XStream
     */
    private static String generateDeployRuleXml(DeployRule rule) throws Exception{
        if (rule == null)
            return null;
        String xml = "<?xml version=\"1.0\"?> \n";
        XStream xstream = new XStream();
        xstream.alias("rule", DeployRule.class);
        xstream.alias("modules", ArrayList.class);
        xstream.alias("datasource", DatasourceModuleVO.class);
        xstream.alias("jms", JmsModuleVO.class);
        xstream.alias("sql", SqlModuleVO.class);
        xstream.alias("ldap", LdapModuleVO.class);
        xstream.alias("application", ApplicationModuleVO.class);
        xml += xstream.toXML(rule);
        return xml;
    }
}

2. EAR Packaging: Extend Packaging to Include XML

After the Application Assembler generates the XML deployment rule, the next step is to package the EAR to include this rule document.

An extended JAVA EE application EAR including the XML rule will have the following structure. An "EXT-INF" directory is defined inside EAR to keep the extended deployment files as depicted in Figure 6.

Figure 6
Figure 6. Extended Java EE EAR file structure

In this sample, an ant script is used to package the EAR. Once the EAR is packaged, the application assembler will deliver this extended EAR to the Deployer.

<project name="ExtPackaging" default="package" basedir="..">
    <property name="ear.dir" value="${basedir}/ear"/>
    <property name="ext.dir" value="${basedir}/ext"/>
    <property name="ldap.dir" value="${basedir}/ldap"/>
    <property name="sql.dir" value="${basedir}/sql"/>
    <property name="etc.dir" value="${basedir}/etc"/>
    <property name="dist" value="${basedir}/dist"/>
    <property name="application.ear" value="SOManager.ear"/>
    <mkdir dir="${ear.dir}/EXT-INF"/>
    <mkdir dir="${ear.dir}/EXT-INF/sql"/>
    <mkdir dir="${ear.dir}/EXT-INF/ldap"/>
    <mkdir dir="${ear.dir}/EXT-INF/etc"/>
    
    <target name="package"
        description="creates custom java ee archive">
        <copy todir="${ear.dir}/EXT-INF" file="${ext.dir}/rule.xml"/>
        <copy todir="${ear.dir}/EXT-INF/sql">
            <fileset dir="${sql.dir}">
                <include name="*.sql"/>
            </fileset>
        </copy>
        <copy todir="{ear.dir}/EXT-INF/ldap">
            <fileset dir="${ldap.dir}">
                <include name="*.ldif"/>
            </fileset>
        </copy>
        <copy todir="{ear.dir}/EXT-INF/etc">
            <fileset dir="${etc.dir}"/>
        </copy>
        <ear destfile="${dist}/${application.ear}" 
            appxml="${ear.dir}/META-INF/application.xml" 
            manifest="${ear.dir}/META-INF/MANIFEST.MF">
            <metainf dir="${ear.dir}/META-INF"/> 
            <fileset dir="${ear.dir}"/>
        </ear>
    </target>
</project>

3. Deployer: Extending Packaging to Include XML Rule

This is the last and most exciting part of the deployment: push one button to kick off the auto-deployment. First, let's see how the sample application works (as depicted in Figure 7):

Figure 7
Figure 7. Sample application of auto-deployment

What happened on the backend?

Design of the Deployer application

The class design is depicted in Figure 8.

Figure 8
Figure 8. Class diagram of the Deployer Application

The deployment flow is shown in Figure 9.

Figure 9
Figure 9. Sequence diagram of the deployment flow

The following is the code sample of the Deployer object:

package onebuttondeploy.deployer;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.apache.log4j.Logger;

import com.thoughtworks.xstream.XStream;

import onebuttondeploy.rule.ApplicationModuleVO;
import onebuttondeploy.rule.DatasourceModuleVO;
import onebuttondeploy.rule.DeployRule;
import onebuttondeploy.rule.JmsModuleVO;
import onebuttondeploy.rule.LdapModuleVO;
import onebuttondeploy.rule.SqlModuleVO;

/**
* @author Colin (Chun) Lu
* @Email colinlucs@gmail.com
*/
public class Deployer {
    private DeploymentProvider provider;
    public static Logger logger = Logger.getLogger(Deployer.class);
    
    
    public Deployer(DeploymentProvider provider) {
        this.provider = provider;
    }
    
    /*
     * Factory method: instantiate the provider object, 
     * and wires this object to deployer instance.
     */
    public static final Deployer getInstance(String providerClass) throws Exception{
        Class c = Class.forName(providerClass);
        DeploymentProvider provider = (DeploymentProvider)c.newInstance();
        return new Deployer(provider);
    }
    
    /*
     * Main deploy method: invokes provider's deploy method, 
     * passing the EAR as byte array
     */
    public String deploy(byte[] appArchive) throws Exception{
        DeployRule rule = parseXmlRule(appArchive);
        String[] moduleResults = provider.deploy(rule, appArchive);
        String result="";
        for (int i = 0; moduleResults != null 
            && i<moduleResults.length; i++) {
            if (i == 0)
                result = moduleResults[i];
            else
                result += "\n" + moduleResults[i];
        }
        return result;
    }    
      /*
     * EAR analyzer method: 
     * 1. extract the xml rule document using JarEntry 
     * 2. De-serialize XML to DeployRule using XStream
     */
    private DeployRule parseXmlRule(byte[] appArchive) throws Exception{
        JarInputStream appEar = new JarInputStream(new ByteArrayInputStream(appArchive));
        JarEntry entry = null;
        StringBuffer strOut = new StringBuffer();
        String xml = null;
        while ((entry = appEar.getNextJarEntry()) != null) {
            String entryName = entry.getName();
            logger.debug("entry name="+entryName);
            if(entryName.equals("EXT-INF/rule.xml")){
                String aux;
                BufferedReader br = new BufferedReader(new InputStreamReader(appEar));
                while ((aux=br.readLine())!=null)
                    strOut.append(aux);
                xml = strOut.toString();
                appEar.close();
                br.close();
                break;
            }
        }
        logger.debug("xml="+xml);
        DeployRule rule = null;
        if (xml != null) {
            XStream xstream = new XStream();    // With XPP3 lib
            xstream.alias("rule", DeployRule.class);
            xstream.alias("modules", ArrayList.class);
            xstream.alias("datasource", DatasourceModuleVO.class);
            xstream.alias("jms", JmsModuleVO.class);
            xstream.alias("sql", SqlModuleVO.class);
            xstream.alias("ldap", LdapModuleVO.class);
            xstream.alias("application", ApplicationModuleVO.class);
            rule = (DeployRule)xstream.fromXML(xml);
        }
        return rule;
    }
}

Benefits

Utilizing this rule-based auto-deployer in your Java EE application deployment task has the following benefits:

Conclusions

This article discussed the challenges in deploying large Java EE applications. And then an intelligent auto-deployer is proposed to make the deployment task easier. This auto-deployer can significantly simplify the deployment task in a large enterprise environment. It also helps the enterprise to maintain a well-defined application deployment flow.

Resources

Colin (Chun) Lu is the systems analyst at Telus Mobility. He has been working in J2EE architecture design and development with WebLogic, and WebSphere since 1999. Currently he is working in the area of SOA for Java EE application design and integration. You can reach Colin at colinlucs@gmail.com


Return to ONJava.

Copyright © 2009 O'Reilly Media, Inc.