HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

You have a task that your Ant build process needs to perform and none of the built-in or dozens of optional tasks fits the bill. If at this point you're thinking that Ant won't work for you, then the authors of Ant have some wonderful news. The framework they use to run built-in tasks is also available for your own task.

If that piques your interest, you'll be happy to know that in the next few paragraphs, I promise you'll have all the information you need to use this framework. If you haven't used Ant yet, read the excellent article by Joey Gibson, "A (Brief) Introduction to Ant" (JDJ, Vol. 7, issue 11), before venturing on this journey.

The framework that supports Ant loosely defines a contract that describes the responsibilities of the task provider. Briefly stated, the task provider provides a class that implements the desired behavior and introduces this task and its supporting class to Ant, exploiting one of the means available for that purpose. The framework is responsible for discovering the task's supporting class and then managing that class's life cycle. Although a task is fronted with a single class, other classes (which may or may not be known to Ant) may be used to help support the completion of the task. Let's take an in-depth look at the custom task contract defined by Ant.

For a class to be considered a custom task it must implement the method, public void execute(). That's it! If you're wondering what the catch is, it's pretty much the same as with everything else. The more information you provide, the less work the framework has to do in order to work with your task. Providing additional information allows the framework to do more work for you. With this in mind, let's describe the portion of the contract we're concerned with here. The description of the providers' responsibilities are as follows:

  • Named task must be supported with a Java class.
  • The class must implement public void execute().
  • The class may extend import org.apache.tools.ant.Task.
  • The class may implement public void init().
  • The class must implement a set method for each attribute of the task.
  • The class should implement the default constructor.
  • The name task must be registered with either the taskdef task or via the addition of an entry into the task.properties file.
  • The execute method should throw a org.apache.tools.ant. BuildException.Complementary to the providers' responsibilities are the frameworks' responsibilities:
  • Call the default constructor for the provider's supporting class.
  • Call the public void init() method once.
  • Call the appropriate set method for every task attribute.
  • Call the public void execute() throws BuildException method once for every occurrence of the corresponding task found in the build file.

    In addition to those assuming these responsibilities, the provider must define a set of XML tags to be used when exploiting the custom task. From here, we could proceed to a fairly dry explanation of how all this fits together, but let's see how it all works in practice instead. I'll kick start the process with the following story.

    Most applications built today contain a number of "off-the-shelf" components that complement code developed in-house. These components can be supplied by a combination of vendors and infrastructure teams that work alongside the application development team. Given that each group (especially third-party suppliers) will have its own delivery schedule over which you most likely won't have any influence, it would be unheard of not to have to deal with version management. Lack of a concise plan to manage versions often results in confusion among configuration management staff that can result in them having to spend a considerable amount of time sorting out which versions of which components (a.k.a. JARs) are to be used for each build. A tactical solution to this problem is to use a version map to describe the build. Let's first build a sample map and then a custom task to use this map to drive a build process.

    To keep things somewhat simplified so we aren't too distracted from our main goal - learning how to build a custom task - we'll purposely limit the functionality of the version map to pulling artifacts from CVS. The test application will be a toy chat application that consists of four layers (see Figure 1).

    The JMS bus was acquired from a third-party vendor (SwiftMQ in this instance). Our internal infrastructure group is responsible for the channel component. Finally, the application development team has built chat client and topic server. During the development process, they've also identified a common layer shared by the clients and the server. In accordance with our earlier constraints, the JMS implementation can be found in the file system. The JAR file for the channel, as well as the source for commons, the chat client, and the topic server will be pulled from CVS. Let's use XML to build the version map.

    Figure 1

    <map application="chat" version="1.0">
    <component source="cvs" name="channel" version="cvs_label" dest="./lib"/>
    <component source="cvs" name="common" version="cvs_label"
    dest="./com mon"/>
    <component source="cvs" name="chat_server" version="cvs_label"
    dest="./serv er"/>
    <component source="cvs" name="chat_client" version="" dest="./client"/>
    </map>

    The map contains all the information needed to identify all the attributes that are necessary to locate a component. Note: This mechanism does rely on a stable policy of how code artifacts are to be stored.

    As mentioned earlier, we're required to register the custom task. This can be completed using one of two techniques. The taskdef task maps an alias for a task to its implementation. The second technique inserts the definition into the task.properties file found in the Ant distribution. A quick examination of the file will reveal how to do this. I'm going to avoid the discussion as to which approach is best, but there are implications from using either mechanism to register your custom task that go beyond technical issues. Let's look at the build file fragment found in the following code.

    <taskdef name="versionmap" class="com.jpt.ant.task.VersionMap">

    <target name="version">
    <versionmap file="chat.xml" dest="." />
    </target>

    This is telling Ant that we'd like to define the custom task aliased as versionmap and implemented in the class com.jpt.ant.task.VersionMap. The task versionmap takes two attributes, the name of the file containing the map and a destination directory to work in.

    Again referring back to the contract, we see that we should create a class that implements our custom behavior. Though it's not necessary, it makes life a lot easier to have our class extend org.apache.tools.ant.Task. Extending this class allows us to inherit common task behavior. We'll have to decide on an implement for the methods public void init() and public void execute(). In addition, the XML defines the two attributes, file and dest. Fields and supporting access methods must be implemented to support these attributes. Let's look at the custom task class template found in Listing 1.

    With this, we've pretty much covered the entire contract. All that's left is to fill in the task's behavior. Let's start with the init method. The init method gets called once and this occurs fairly early on in the build process. Consequently, we should embed behavior that we only want executed once and that can also occur early in the build process. Since our custom task doesn't have any behavior that fits this description, we allow the call to percolate up to the super classes implementation.

    A common technique used for coding the execute method is to defer the bulk of the implementation to a helper method called execute0. Doing this allows us to easily handle the inherited attribute, failonerror, in the execute method. The main logic that we need is to read each entry in the version map and use the information to trigger a CVS get (see Listing 2). Note how the functionality of other tasks is being used to help us achieve our goal.

    In the execute0() method, the XML file is converted into a map. An iterator runs through each entry in the map and calls its execute method using the task as a parameter. The execute method of the task is then free to make a call back on the getSourceFromCVS method. It's this method that creates an instance of a CVS (from the optional tasks list) task and then configures it. The executeTask method sets the technical parameters on the task before executing it. Our simplified custom task is now complete.

    Where to Go from Here
    Nested attributes can add a lot of flexibility to your custom task. Each Ant data type object is supported by a class and, consequently, the development path is fairly similar to one that's followed by custom tasks. In addition to tasks and data types, you can also build specialized build listeners. Though their development path is slightly different than that of data types and tasks, it's still not all that difficult. I've always found that it's best to look into Ant's code when I'm looking for examples or techniques to help me develop custom tasks, data types, and listeners. Happy AntingS

    References

  • Ant 1.5.1 source code: www.apache.org
  • Williamson, A., et al. (2003). Ant Developer's Handbook. Sams.

    About The Author
    Kirk Pepperdine is the chief technical officer at Java Performance Tuning.com and has been focused on object technologies and performance tuning for the last 15 years. Kirk is a co-author of Ant Developer's Handbook (Sams). [email protected]

    "Customizing Ant"
    Vol. 8, Issue 9, p. 38

    	
    
    
    
    Listing 1 Code template for a custom task
    
    import org.apache.tools.ant.Task;
    import org.apache.tools.ant.BuildException;
    
    public class VersionMap extends Task {
    
        private String file;
        private String dest;
    
        public VersionMap() {
        }
    
        public void setFile(String file) {
            this.file = file;
        }
    
        public void setDest( String dest) {
            this.dest = dest;
        }
    
        public void execute() throws BuildException {
            try {
                execute0();
            } catch (BuildException be) {
                if (failonerror)
                    throw be;
                else
                    log(be.getMessage());
            }
        }
    }
    
    
    
    Listing 2  Triggering a get from CVS
    
        public void execute0() throws BuildException {
            try {
                this.createSourceDir( this.dest);
                VersionMap map = VersionMap.createFrom( this.file);
                Iterator iter = map.iterator();
                while ( iter.hasNext())
                    ((VersionMapEntry)iter.next()).processMapEntry( this);
            } catch (Exception e) {
                throw new BuildException(e);
            }
        }
    
        private void executeTask(Task task) throws BuildException {
            task.setProject(this.getProject());
            task.setLocation(this.getLocation());
            task.setOwningTarget(this.getOwningTarget());
            task.init();
            task.execute();
        }
    
        public void getSourceFromCVS(String moduleName, String version, String dest)
        throws BuildException {
            Cvs task = new Cvs();
            task.setCommand("checkout");
            task.setPackage(moduleName);
            task.setTag(version);
            task.setDest(dest);
            this.executeTask(task);
        }
    
    

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.