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


Java Software Automation with Jakarta Ant

by Eugene Kuleshov and Dmitry Platonoff
07/24/2002

Jakarta Ant has become a widely accepted standard for Java project build automation. It is not only a tool, but also a very powerful language that allows you to describe complex build and deployment scenarios. At the same time, it is not a scripting language, but a process-oriented language. The convenient thing about Ant is that it's XML-based, so you can easily generate and edit its build files with lots of tools. And finally, Ant is also an open platform and a framework allowing you to plug in new functionality. All of these things make Ant more than suitable for the role of a general purpose automation tool.

If you would like to automate the following tasks, then there's a good chance that you should consider adding Ant support into your product:

All of these features can be used in any combination with each other. You can find the complete list of Ant tasks on the Jakarta Ant Web site. And if those are not enough, a customer can always add her own features by implementing a standard Ant task.

Execute Ant From Your Application

We assume that you are familar with Ant's Projects, Targets, and Properties, and that you know what the build file looks like. All of those things will be the same even if you're going to call Ant from your application. All you need is to complete the following steps:

Let's take a look at each step in more detail. The key class in the Ant framework is the org.apache.tools.ant.Project class. You can create the entire Project structure by invoking the appropriate methods on this class; however, it's not the most convenient way to work with an Ant project. Instead, you can use a default ProjectHelper implementation to parse a good old build.xml file. The most attractive part of the Ant integration is the possibility of using standard and familiar build.xml files.

  import java.io.*;

  import org.apache.tools.ant.*;

  ...

  Project project = new Project();
  project.init();

  File buildFile = new File( baseDir, "build.xml");
  ProjectHelper.configureProject( project, buildFile);

You can pass property values to your Ant project. There are two types of properties. A user property cannot be overwritten or redefined within the build process. To specify it, you can use the following command:

project.setUserProperty( name, value);

A project property can be redefined within the build process. To populate these, use the following command:

project.setProperty( name, value);

For example, you can use the GUI to let the customer edit certain properties, or load a properties file from the hard drive, or obtain them from some other source.

  import java.io.*;
  import java.util.*;

  ...

  Properties prop = new Properties();
  prop.load( new FileInputStream( "build.properties"));
  for( Enumeration en = prop.keys(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    project.setProperty( key, prop.getProperty( key));
  }

To be able to monitor what's happening during the build, you should register a BuildListener. There're a number of standard listeners available:

You can also provide your own implementation of the BuildListener interface to receive events when the build is started or finished, a target is started or finished, a task is started or finished, or when a message is logged.

  project.addBuildListener( new DefaultLogger()); 
  project.addBuildListener( new MailLogger()); 

In case a user interaction is required during the build process, Ant provides an InputHandler interface abstraction. With this, you can register one of the standard input handlers, such as the DefaultInputHandler, that outputs the prompt to the System.out and reads the input from System.in. Another handler is the PropertyFileInputHandler, which uses the prompt as a key to look up a value in the property file. The name of the file is read from the ant.input.properties system property.

  import org.apache.tools.ant.input.*;

  ...

  project.setInputHandler( new PropertyFileInputHandler());

Once you have a Project instance configured, you can execute any target defined in this particular build.xml file by using the target name:

project.executeTarget( targetName);

That's about it. Now we can look at some practical applications.

Practical Example

Imagine that you need to do some automation in your Web application, say, in a servlet. You can find the complete source code in the references section.

Servlets are multithreaded applications, so we need to create a separate temporary directory for each customer. We're going to use session id for that.

  ...

  protected void service( HttpServletRequest req, HttpServletResponse res) 
        throws ServletException, IOException {
    HttpSession session = req.getSession();    

    String baseDir = getServletContext().getRealPath( "/WEB-INF/ant/");
    String dir = baseDir+session.getId().hashCode();
    File ff = new File( dir);
    if( !ff.exists()) {
      boolean rc = ff.mkdir();
      if( !rc) throw new Exception( "Unable to create directory: "+dir);
    }

    ...

    Project project = new Project();

    File buildFile = new File( baseDir, "build.xml");
          
    project.setUserProperty( "session.id", ""+session.getId().hashCode());
    project.setUserProperty( "ant.file", buildFile.getAbsolutePath());
    project.setUserProperty( "ant.version", Main.getAntVersion());

    project.setBaseDir( new File( baseDir));
    project.init(); 
    ProjectHelper.configureProject( project, buildFile);

    ...

Because of the servlet specifics, we will need to implement our own BuildListener and InputHandler.

The first class is the HtmlReportLogger, which will use a PrintWriter from the HttpServletRequest to prepare an HTML page with the build results to return to the Web browser.

project.addBuildListener( new HtmlReportLogger( res.getWriter()));

The second class is the ServletLogger, which utilizes the ServletContext logging facilities to write the report to the application server log.

project.addBuildListener( new ServletLogger( this));

The InputHandler implementation is using the properties from the Ant Project to retrieve requested values.

  import org.apache.tools.ant.*;
  import org.apache.tools.ant.input.*;


  public class ProjectInputHandler implements InputHandler {
    private Project project;

    public ProjectInputHandler( Project project) {
      this.project = project;
    }

    public void handleInput( InputRequest request) throws BuildException {
      Object o = project.getProperty( request.getPrompt());
      if( o==null) 
        throw new BuildException( "Unable to find input for \'"+
            request.getPrompt()+"\'");

      request.setInput( o.toString());
      if( !request.isInputValid())
        throw new BuildException( "Found invalid input "+o+
            " for \'"+request.getPrompt()+"\'");
    }

  }

To populate said project properties, we will use the parameters supplied in the Web application environment.

  ...

  // servlet Init parameters
  for( Enumeration en = getInitParameterNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    project.setUserProperty( key, getInitParameter( key));
  }

  // ServletContext
  for( Enumeration en = context.getInitParameterNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    project.setUserProperty( key, context.getInitParameter( key));
  }
  for( Enumeration en = context.getAttributeNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    Object atr = context.getAttribute( key);
    if( atr!=null) project.setUserProperty( key, atr.toString());
  }
          
  // HttpSession
  for( Enumeration en = session.getAttributeNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    Object atr = session.getAttribute( key);
    if( atr!=null) project.setUserProperty( key, atr.toString());
  }

  // Servlet Request
  for( Enumeration en = request.getAttributeNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    Object atr = request.getAttribute( key);
    if( atr!=null) project.setUserProperty( key, atr.toString());
  }
  for( Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    project.setProperty( key, request.getParameter( key));
  }

  // HttpServlet Request
  for( Enumeration en = request.getHeaderNames(); en.hasMoreElements(); ) {
    String key = ( String) en.nextElement();
    project.setProperty( key, request.getHeader( key));
  }

  ...

Our AntServlet is also employing the com.oreilly.servlet package to manage file uploads in the temporary session directory.

  ...

  protected void service( HttpServletRequest req, HttpServletResponse res) 
        throws ServletException, IOException {
    HttpSession session = req.getSession();    
    ServletContext context = getServletContext();
    
    String dir = context.getRealPath( "/WEB-INF/ant/"+session.getId().hashCode());
   
    HttpServletRequest request = req;

    String type = req.getHeader( "Content-Type");
    if( type!=null && type.startsWith( "multipart/form-data"))
      request = new MultipartWrapper( req, dir);

    ...

Now we are ready to prepare a simple build.xml file for our Web application automation that will pick up all of the uploaded files, compress them into a zip archive, and then send an email with this fille attached.

  <?xml version="1.0"?>

  <project default="main" basedir=".">

    <target name="main">
      <echo message="Ant started for id ${session.id}"/>
      <echo message="${uname}"/>
      <echo message="${uemail}"/>
      <zip destfile="${session.id}/${uname}.zip">
        <fileset dir="${session.id}" includes="*" excludes="**/${uname}.zip"/>
      </zip>
      <mail from="AntServlet@hotmail.com" tolist="${uemail}" 
            subject="test" mailhost="localhost">
        <message>Hi ${uname}</message>
        <fileset dir="${session.id}" includes="**/${uname}.zip"/>
      </mail>
      <delete dir="${session.id}"/>
    </target>

  </project>

You can use the following simple HTML form to invoke our AntServlet. It allows you to supply the uname and uemail parameters, and pick the files for upload.

  <html>
  <body bgcolor=white>

  AntServlet
  <p>
  <FORM ACTION="AntServlet" ENCTYPE="multipart/form-data" METHOD=POST>
  name<BR>
  <INPUT TYPE="TEXT" NAME="uname"><BR>
  email<BR>
  <INPUT TYPE="TEXT" NAME="uemail"><BR>
  file 1<BR>
  <INPUT TYPE="FILE" NAME="file1"><BR>
  file 2<BR>
  <INPUT TYPE="FILE" NAME="file2"><BR>
  file 3<BR>
  <INPUT TYPE="FILE" NAME="file3"><BR>
  file 4<BR>
  <INPUT TYPE="FILE" NAME="file4"><BR>
  <INPUT TYPE=SUBMIT>
  </FORM>

  </body>
  </html>

Before deploying and testing this sample application, you might want to package it as a .WAR file. The AntServlet.war file should have the following structure:

Related Reading

Ant: The Definitive Guide
By Jesse E. Tilly, Eric M. Burke

  AntServlet.war
    \index.html
    \WEB-INF\
        \web.xml
        \ant\
        |   \build.xml
        \lib\
        |   \ant.jar
        |   \optional.jar
        |   \xercesImpl.jar
        |   \xml-apis.jar
        |   \activation.jar
        |   \mail.jar
        |   \mailapi.jar
        |   \smtp.jar
        |   \cos.jar
        \classes\
            \com\
                \ru2\
                    \ant\
                        \AntServlet.class
                        \AntServlet.java
                        \HtmlReportLogger.class
                        \HtmlReportLogger.java
                        \ProjectInputHandler.class
                        \ProjectInputHandler.java
                        \ServletLogger.class
                        \ServletLogger.java

The following libraries are required:

The Web application descriptor, web.xml, looks like this:

  <web-app>
    <servlet>
      <servlet-name>AntServlet</servlet-name>
      <servlet-class>com.ru2.ant.AntServlet</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>AntServlet</servlet-name>
      <url-pattern>/AntServlet</url-pattern>
    </servlet-mapping>
  </web-app>

Conclusion

The given example demonstrates the simplicity and flexibility of using Ant-driven build scenarios for your custom software automation. It shows how the same Ant script can be called from the command prompt as well as from a GUI, or even from inside of the Web container.

References

Eugene Kuleshov is an independent consultant with over 15 years of experience in software design and development.

Dmitry Platonoff is a Senior Developer with Think Dynamics Canada, specializing in web application architecture and user interface concepts.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.