ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Writing Ant Tasks
Pages: 1, 2

The Jing Task

Now let's explore a more complex task. James Clark has written an Ant task for Jing. As I mentioned earlier, Jing is an open source, Java validator for the RELAX NG and Schematron schema languages for XML. Jing is reliable and provides good performance, and is a good choice for those wanting to use Ant to automate the validation of documents with an XML pipeline -- the execution of a succession of XML tasks.



To use this task, you need to download the Jing binaries and place jing.jar in the Ant lib subdirectory (or elsewhere in the classpath using -lib). You can also download the Jing source code (see src.zip), where you will find JingTask.java. For your convenience, I have included the source file JingTask.java in the example archive, along with the Jing license (copying.txt). JingTask.java is too long to list here in its entirety, but I will highlight parts of the code in general terms in the discussion that follows.

The JingTask.java source should provide some hints on writing more complex Ant tasks. In Add.java, all of the task code is neatly self-contained within that class. Of course, in most tasks, the task code comes from external code for an existing program that must be imported into the task code. To illustrate, near the top of JingTask.java, you will notice that it imports many more classes than Add.java, 18 to be exact: eight from Jing, five from Ant, two from SAX, and three from Java itself:

import com.thaiopensource.util.PropertyMapBuilder;
import com.thaiopensource.validate.Flag;
import com.thaiopensource.validate.SchemaReader;
import com.thaiopensource.validate.ValidationDriver;
import com.thaiopensource.validate.schematron.SchematronProperty;
import com.thaiopensource.validate.rng.CompactSchemaReader;
import com.thaiopensource.validate.rng.RngProperty;
import com.thaiopensource.xml.sax.ErrorHandlerImpl;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import java.io.File;
import java.io.IOException;
import java.util.Vector;

JingTask extends Ant's Task class, and declares a set of private variables, mostly for the management of files or filesets:

/**
 * Ant task to validate XML files using RELAX NG
 * or other schema languages.
 */

public class JingTask extends Task {

  private File schemaFile;
  private File src;
  private final Vector filesets = new Vector();
  private PropertyMapBuilder properties =
            new PropertyMapBuilder();
  private boolean failOnError = true;
  private SchemaReader schemaReader = null;

The filesets variable of type Vector will be used for code that manipulates sets of files within the context of a task (search for filesets in JingTask.java to see how this code is pieced together). The properties variable of type PropertyMapBuilder is used with Jing's ValidationDriver.

Next, the task sets up an error handler, LogErrorHandler, an extension of Jing's ErrorHandlerImpl, which in turn is an implementation of SAX's ErrorHandler interface:

private class LogErrorHandler extends
   ErrorHandlerImpl {

   int logLevel = Project.MSG_ERR;

   public void warning(SAXParseException e)
     throws SAXParseException {

     logLevel = Project.MSG_WARN;
     super.warning(e);

   }

   public void error(SAXParseException e) {
     logLevel = Project.MSG_ERR;
     super.error(e);
   }

   public void printException(Throwable e) {
     logLevel = Project.MSG_ERR;
     super.printException(e);
   }

   public void print(String message) {
     log(message, logLevel);
   }
}

The message constants MSG_ERR and MSG_WARN come from Ant's Project class. The warning, error, and printException methods are called from the superclass ErrorHandlerImpl. The print method calls the log method, which you can find in Task and in Task's superclass, Ant's ProjectComponent.

When the JingTask constructor is defined, it sets the RELAX NG property so that it checks ID/IDREF/IDREFS by default, if they exist in the instance:

public JingTask() {
   RngProperty.CHECK_ID_IDREF.add(properties);
}

The execute method overrides the method of the same name from Ant's Task class. This is the most complex part of the code. Basically, this implementation of execute sets up the task for the retrieval of instance and schema files for Jing and, unlike the Add task, does a fair amount of error handling. If the expected files or filesets are not found, this part of the code will throw a SAX, I/O, or build exception, depending on the problem.

At the beginning, the method checks to see if the values in the rngFile attribute or the schemaFile attribute are in place, then if either a file attribute or a fileset element exists:

if (schemaFile == null)
    throw new BuildException("There must be an
       rngFile or schemaFile attribute",location);

if (src == null && filesets.size() == 0)
    throw new BuildException("There must be a file
       attribute or a fileset child element",
       location);

If a value for rngFile is not present, there must be a value for schemaFile; if file is not present, the fileset element must be a child of the jing element.

Following these tests, an error handler is instantiated and an error flag (hadError) is set up. Next, a try block contains the code to feed file names to Jing using ValidationDriver. After the try block, several catch blocks and an if statement stand sentinel to handle any errors.

After the execute method, JavaTask defines setter methods for eight possible attributes, namely setCheckid, setCompactsyntax, setFailonerror, setFeasible, setFile, setPhase, setRngfile, and setSchemafile. The following table shows the correspondence between these methods, Jing's command-line options, and Ant task attributes.

Table 1. Jing methods and attributes

MethodAttributeOption Description
setCheckidcheckidChecks for ID/IDREF/IDREFS compatibility. Corresponds to -i. Possible values are true or false, with default true.
setCompactsyntaxcompactsyntaxTurns compact syntax checking on or off. Corresponds to -c. Possible values are true or false, with default false.
setFailonerrorfailonerrorA value of false means that processing continues after an error occurs.
setFeasiblefeasibleChecks if document is feasibly valid. Corresponds to -f. Possible values are true or false with default false.
setFilefileName of XML file to validate.
setPhasephaseSchematron phase to be used during internal schema creation or validation (not demonstrated here).
setRngFilerngfileName of RELAX NG schema file.
setSchemafileschemafileName of Schematron file (not demonstrated here).

The final bit of code in JingTask.java is the addFileset method, a utility method for fileset functionality. Search for filesets in the code to see how it works.

Following is another build file from the archive (build-jing.xml) that exercises the Jing task:

<?xml version="1.0"?>

<project default="jing">

<taskdef name="jing" classname="com.thaiopensource.relaxng.util.JingTask"/>

<target name="jing">
 <jing file="time.xml" rngfile="time.rng"/>
</target>

</project>

taskdef identifies the name of the task (jing) and the name of the class that holds the code for the task (com.thaiopensource.relaxng.util.JingTask). The only child of target is the jing element, with the attributes file and rngfile. The jing element triggers the Jing task, telling Jing to validate time.xml against time.rng.

Now invoke Ant with build-jing.xml. This assumes that you have placed jing.jar in the Ant lib directory. In the working directory, type this command:

ant -f build-jing.xml

This will be the result if all files are in place:

Buildfile: build-jing.xml

jing:

BUILD SUCCESSFUL
Total time: 1 second

If Jing runs quietly, that indicates success. Two other Jing-related build files are in the example archive: build-jing-compact.xml, which validates a file against a compact syntax schema, and build-jing-set.xml, which validates a set of files against a RELAX NG schema. Test both these files yourself with ant -f. The inner workings of these build files should now be self-evident.

Conclusion

After reading this article, the basic steps of writing an Ant task should no longer be a mystery to you. To write a task for a program as complex as Jing, however, you need to have an intimate understanding of the code so that you know how to feed the program what it needs to run properly. You'll also want to add error handling of some sort for an industrial-strength task.

To expand your understanding of creating tasks for Ant, you'll can review the section in Ant's official online manual that describes how to write your own task, as well as the tutorial on writing tasks. The Ant site also provides a list of external tasks (tasks written for Ant by third parties) that may be of interest.

Resources

Michael Fitzgerald is Principal at Overdue Books, a publishing and writing consultancy.


Return to ONJava.com.