9.3 Setting Up Ant to Run XDoclet

9.3.1 Problem

You want to integrate file generation into the Ant build process.

9.3.2 Solution

Modify your Ant buildfile to create the directory structure as specified in the previous recipe and execute an xdoclet.DocletTask or subclass. This recipe creates a task definition for xdoclet.modules.ejb.EjbDocletTask and names it ejbdoclet.

9.3.3 Discussion

A successful XP project understands the need for continuous integration. Continuous integration means a successful build of the project, including complete generation of all out-of-date generated files and 100% passing unit tests. With that said, here's how generating source files improves the continuous integration process. Here is a typical Ant build process:

  1. Prepare the development environment by creating output directories.

  2. Compile out-of-date code.

  3. Package the code into a deployable unit (JAR, WAR, or EAR).

  4. Execute the JUnit tests.[1]

    [1] For server-side testing, you'll have to deploy before running tests.

  5. Deploy to a server.

If any task fails the build should stop and a friendly message should be reported. Code generation adds one more step to this process:

  1. Prepare the development environment by creating output directories.

  2. Run XDoclet to regenerate out-of-date generated source files.

  3. Compile out-of-date code.

  4. Package the code into a deployable unit (JAR, WAR, or EAR).

  5. Execute the JUnit tests.

  6. Deploy to a server.

Adding the code-generation step requires modifying the Ant buildfile. The first step is to define a task definition for an xdoclet.DocletTask task. This recipe uses the xdoclet.modules.ejb.EjbDocletTask class, which extends xdoclet.DocletTask and is provided by XDoclet. When defining the task, a valid classpath must be set up, too. Here is how to define this task:[2]

[2] XDoclet Version 1.2. beta 1 did not include the Jakarta Commons Logging 1.0 JAR file. We included the JAR file in our project's lib directory.

<taskdef name="ejbdoclet" classname="xdoclet.modules.ejb.EjbDocletTask">
  <classpath>
    <pathelement path="${env.JBOSS_DIST}/client/jboss-j2ee.jar"/>
    <pathelement path="${env.XDOCLET_HOME}/lib/xdoclet.jar"/>
    <pathelement path="${env.XDOCLET_HOME}/lib/xjavadoc.jar"/>
    <pathelement path="${env.XDOCLET_HOME}/lib/xdoclet-ejb-module.jar"/>
    <pathelement location="${dir.lib}/commons-logging-1.0.jar"/>
  </classpath>
</taskdef>

Next, set up a few properties that define the development environment. As discussed in the first recipe, the generated source files are placed into the src-generated directory specified by the dir.generated.src property, and the compiled generated code gets placed into the build-generated directory that is specified by the property dir.generated.build. These directories are at the same level as the build and src directories, allowing for easy management. Ant properties specify where to place generated deployment descriptors, too.

<property name="dir.build" value="build"/>
<property name="dir.src" value="src"/>
<property name="dir.generated.src" value="src-generated"/>
<property name="dir.generated.build" value="build-generated"/>
<property name="dir.ejb.metainf" value="${dir.generated.src}/ejb/META-INF"/>

The next step is to create a target that sets up the development environment. Here is a target that creates the build and the generated source directories.

<target name="prepare">
  <mkdir dir="${dir.build}"/>
  <mkdir dir="${dir.generated.build}"/>
  <mkdir dir="${dir.generated.src}"/>
  <mkdir dir="${dir.ejb.metainf}"/>
</target>

Finally, create a target that invokes the XDoclet Ant task, which in this recipe is the ejbdoclet task. The details of the ejbdoclet task are discussed in recipes to follow. The example below is just one configuration that can be used to generate EJB code.

<ejbdoclet
  ejbspec="2.0"
  destdir="${dir.generated.src}"
  excludedtags="@version,@author,@see"
  force="${force.ejb}">

  <!-- Rename any package called 'ejb' to 'interfaces'. -->
  <packageSubstitution packages="ejb" substituteWith="interfaces"/>

  <fileset dir="${dir.src}">
    <include name="**/ejb/*Bean.java"/>
  </fileset>

  <homeinterface/>
  <remoteinterface/>
  <session/>
  <deploymentdescriptor destdir="${dir.ejb.metainf}" validatexml="true"/>
</ejbdoclet>

Here's a target that deletes all generated files:

<target name="clean.generated"
    description="Deletes the 'generated.src' and 'generated.build' directories">
  <delete dir="${dir.generated.src}"/>
  <delete dir="${dir.generated.build}"/>
</target>

Next is a target that shows how to compile the code, both handwritten and generated. First, this target compiles the handwritten code into the build directory. Next, the generated code is compiled into the build-generated directory. Finally, the client is compiled into the build directory.

<target name="compile.ejb" depends="prepare,generate.ejb">
  <!-- compile non-generated server code to the build directory -->
  <javac srcdir="${dir.src}" destdir="${dir.build}">
    <classpath refid="classpath.ejb"/>
    <include name="**/ejbdoclet/ejb/"/>
  </javac>

  <!-- compile generated code to the build-generated directory -->
  <javac srcdir="${dir.generated.src}" destdir="${dir.generated.build}">
    <classpath refid="classpath.ejb"/>
    <include name="**/ejbdoclet/"/>
  </javac>

  <!-- compile non-generated client code to the build directory -->
  <javac srcdir="${dir.src}" destdir="${dir.build}">
    <classpath refid="classpath.ejb"/>
      <include name="**/ejbdoclet/client/"/>
  </javac>
</target>

More than likely you will need to create two compilation targetsone for handwritten code and the other for generated code. The only time generated code needs to be recompiled is when generated source file templates change. If you are using XDoclet to generate EJB code, you definitely want to separate out the compilation process once your EJB code becomes solid and does not change often. This dramatically speeds up your builds.

To prevent XDoclet from running again and again, use the Ant uptodate task. For example, generate a temporary file, say ejbdoclet.done, and then update the source fileset with the temporary file. If a file is newer than the temp file, XDoclet should regenerate the files; otherwise, skip the XDoclet process.

9.3.4 See Also

Recipe 9.2 discusses where generated source files should go in a development environment. Recipe 9.5 shows how to use XDoclet to generate an EJB deployment descriptor. Recipe 9.7 shows how to generate EJB home and remote interfaces. To download the Jakarta Commons Logging API, visit http://jakarta.apache.org/commons/logging.html.