Monday, 12 December 2016

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

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.

Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer Part-2
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.

Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer Part-2
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.
  • Deployment XML rule: rule.xml
  • SQL files: used by SQL deployment modules specified at the rule document
  • LDIF files: used by LDAP deployment modules to upload/expose system configurations
  • Other resource files that are specified and used in the rule
Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer Part-2
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):
  • Browse and upload the EAR
  • Push the Deploy button
  • Done!
Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer Part-2
Figure 7. Sample application of auto-deployment
What happened on the backend?
  • First, The Deployer Java object wires the real deployment provider class, which is implemented for the specific application server. For example, WebLogic, WebSphere, and JBoss.
  • Then, the Deployer Java object parses the EAR file to retrieve the rule.xml and de-serializes XML to the Rule POJOs.
  • Finally, the Deployer Java object calls the provider's deploy() method by passing the EAR and the Rule object. The provider object deploys DataSources, JMS, and executes SQL statements, and/or uploads system configuration details to LDAP, etc.
Design of the Deployer application
  • Application Server Independent: an Inversion of Control (IoC) design pattern is used in the design of the Deployer to keep it independent of the application server. Different DeployProvider classes are implemented for major Java EE application servers, such as WebLogic, WebSphere, and JBOSS. The Deployer Java object wires the real provider class at runtime based on the deployment requirement. Since the providers are pluggable, the system can easily support different application servers, and its core part is vender independent.
  • Rule-based deployment: a standard, vendor-independent XML rule is designed to instruct the deployer for the deployment. The XStream utility library is used to bind the XML document to Rule POJOs. The rule is also easy to extend to support new requirements.
  • Extended deployments: various deployment module POJOs are designed to extend the deployable modules and tasks. Not only DataSource and JMS can be deployed automatically, SQL execution, LDAP configuration setup can also be supported. Moreover, a new deployment module POJO can be easily designed to applied to the deploy provider class.
  • EAR Analyzer: uses Java's Jar utility API to parse and analyze the EAR file, and extracts the interested files (i.e., rule.xml, SQL files, LDIF files, etc.) from the EXT-INF/ of the EAR.
The class design is depicted in Figure 8.

Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer Part-2
Figure 8. Class diagram of the Deployer Application

The deployment flow is shown in Figure 9.

Using XML and Jar Utility API to Build a Rule-Based Java EE Auto-Deployer Part-2
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:
  • Significantly simplified deployment task: as you can see, this auto-deployer application can really achieve the goal of deployment to be a pushing one-button task.
  • Well-defined deployment flow: the auto-deployer application helps to clearly define the responsibilities of developer, assembler, and deployer. It releases the heavy load of an application deployer by letting an assembler make a well-defined deployment rule, and the deployer doesn't need to know the technical dependencies and requirements of each application. This makes the deployment much more efficient.
  • Centralized and transacted deployment management: In a very large enterprise environment, applications are usually deployed on to hundreds of different systems. This deployer application provides a good mean of a centralized management. In addition, it can manage deployment as a transacted action by implementing un-deploy/re-deploy methods. Therefore, the deployer can rollback the deployment or switch to different version very easily.
  • Application server independent: it is happening that a few different vendors' application servers are running together in a very large enterprise environment. Because the core of this auto-deployer application is vendor independent. It is very easy to support heterogeneous application servers by developing different deploy providers and plug in to the application.