15.3. XDoclet

XDoclet is an open-source project hosted at http://xdoclet.sourceforge.net. It is an extension of Sun's Javadoc tool that allows you to embed well-formed tags in Java source code and output metadata of a particular type. The latest versions of XDoclet include JDO doclet that can create JDO metadata files.

In order to use XDoclet, you will need to download the XDoclet libraries separately from http://xdoclet.sourceforge.net and add the xdoclet-<version>.jar, xdoclet-jdo-module-<version>.jar, and xjavadoc-<version>.jar libraries to your CLASSPATH. Kodo has been tested with XDoclet version 1.2b3 and XJavadoc version 1.0. Any higher version should work as well.

The example below shows how to comment your code with XDoclet JDO tags. The source code should be self-explanatory.

Example 15.13. Commenting for XDoclet

package samples.xdoclet;


import java.util.*;


/**
 * <p>This is a simple example class using XDoclet. It is used to demonstrate 
 * automatic generation of the JDO Metadata file based on the 
 * <code>jdo.*</code> XDoclet tags in the source code.</p>
 *
 * <p>The JDO tags above the class element apply to class-level metadata.  The
 * <code>jdo.persistence-capable</code> tag is required to denote a persistent
 * class.  The tag takes the same attribues as the <code>class</code> element
 * in standard JDO metadata.  You can include class-level vendor extensions 
 * with the <code>jdo.class-vendor-extension</code> tag.</p>
 *
 * @jdo.persistence-capable
 *    identity-type="application"
 *    objectid-class="Main$Id"
 * @jdo.class-vendor-extension
 *    vendor-name="kodo"
 *    key="data-cache-timeout"
 *    value="10"
 */
public class Main
{
    /**
     * Field-level metadata is declared with the <code>jdo.field</code> tag.
     * It takes the same attributes as the <code>field</code> element in
     * standard JDO metadata.
     *
     * @jdo.field
     *    primary-key="true"
     * @jdo.field-vendor-extension
     *    vendor-name="kodo"
     *    key="jdbc-auto-increment"
     *    value="true"
     */
    private String pk1;

    /**
     * You are not required to place all attributes on a separate line as
     * we have been doing above.
     *
     * @jdo.field primary-key="true"
     * @jdo.field-vendor-extension vendor-name="kodo" 
     *    key="jdbc-size" value="20"
     */
    private String pk2;

    /**
     * @jdo.field
     *    null-value="exception"
     *    default-fetch-group="false"
     */
    private String name;

    private Main main;

    /**
     * @jdo.field
     *    default-fetch-group="true"
     *    collection-type="collection"
     *    element-type="Main"
     *    embedded-element="false"
     * @jdo.field-vendor-extension
     *    vendor-name="kodo"
     *    key="inverse-owner"
     *    value="main"
     */
    private Collection nodes = new ArrayList ();

    /**
     * @jdo.field
     *    collection-type="map"
     *    key-type="String"
     *    value-type="Integer"
     */
    private Map cache = new HashMap ();


    public String getPk1 ()
    {
        return this.pk1;
    }


    public void setPk1 (String pk1)
    {
        this.pk1 = pk1;
    }


    public String getPk2 ()
    {
        return this.pk2;
    }


    public void setPk2 (String pk2)
    {
        this.pk2 = pk2;
    }


    public String getName ()
    {
        return this.name;
    }


    public void setName (String name)
    {
        this.name = name;
    }


    public Main getMain ()
    {
        return this.main;
    }


    public void setMain (Main main)
    {
        this.main = main;
    }


    public Collection getNodes ()
    {
        return this.nodes;
    }


    public Map getCache ()
    {
        return this.cache;
    }


    /**
     * Application identity class.
     */
    public static class Id
    {
        private static final char DELIM = '/';

        public String pk1;
        public String pk2;


        public Id ()
        {
        }


        public Id (String serialized)
        {
            int idx = serialized.indexOf (DELIM);
            pk1 = serialized.substring (0, idx);
            pk2 = serialized.substring (idx + 1);
        }


        public boolean equals (Object other)
        {
            if (other == this)
                return true;
            if (!(other instanceof Id))
                return false;

            Id id = (Id) other;
            return pk1.equals (id.pk1) && pk2.equals (id.pk2);
        }


        public int hashCode ()
        {
            return pk1.hashCode () + pk2.hashCode ();
        }


        public String toString ()
        {
            return pk1 + DELIM + pk2;
        }
    }
}

XDoclet does not include direct support for nested vendor extensions, which are required to perform O/R mapping in metadata. Instead, Kodo allows you to simulate nested extensions by specifying key attribute paths separated by the / character. For example, to simulate the following class declaration and extensions:

<class name="Magazine">
    <extension vendor-name="kodo" key="jdbc-class-map" value="base">
        <extension vendor-name="kodo" key="table" value="MAG"/>
        <extension vendor-name="kodo" key="pk-column" value="ID"/>
    </extension>
    <extension vendor-name="kodo" key="jdbc-version-ind" value="version-number">
        <extension vendor-name="kodo" key="column" value="JDOVERSION"/>
    </extension>
    ...
</class>

You could use XDoclet comments as follows:

/**
 * @jdo.persistence-capable
 *
 * @jdo.class-vendor-extension
 *    vendor-name="kodo" key="jdbc-class-map" value="base"
 * @jdo.class-vendor-extension
 *    vendor-name="kodo" key="jdbc-class-map/table" value="MAG"
 * @jdo.class-vendor-extension
 *    vendor-name="kodo" key="jdbc-class-map/pk-column" value="ID"
 *
 * @jdo.class-vendor-extension
 *    vendor-name="kodo" key="jdbc-version-ind" value="version-number"
 * @jdo.class-vendor-extension
 *    vendor-name="kodo" key="jdbc-version-ind/column" value="JDOVERSION"
 */

XDoclet can only be invoked from Ant 1.5 or higher. You can download Ant at http://jakarta.apache.org/ant/. XDoclet also requires Log4J, which ships with Kodo.

The example below shows an ant task you might use to invoke XDoclet. Pay close attention to the comments in the source, as XDoclet is picky about how you invoke it.

Example 15.14. Invoking XDoclet with Ant

<taskdef name="jdodoclet" classname="xdoclet.modules.jdo.JdoDocletTask"/>
<target name="xdoclet">
    <echo>
==========================================
Generating .jdo files from all .java files
==========================================
    </echo>

    <!-- jdoclet seems to require that the embedded fileset's -->
    <!-- dir attribute is set to the root of your classpath,  -->
    <!-- which in this example we're assuming is ${basedir}   -->
    <jdodoclet destdir="${basedir}">
        <fileset dir="${basedir}">
            <include name="samples/xdoclet/*.java"/>
        </fileset>

        <!-- this inner task is required to generate metadata; -->
        <!-- the project attribute specifies the name of the   -->
        <!-- generated .jdo file; the "generation" attribute   -->
        <!-- should be set to "project" to generate single     -->
        <!-- "package.jdo" files per-package, or "class" to    -->
        <!-- generate metata on a per-class basis, such as     -->
        <!-- "MyClass.jdo"                                     -->
        <jdometadata project="package" generation="project"/>
    </jdodoclet>
</target>

Running the above task on the Main shown in our previous example will produce a package.jdo file like this:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo PUBLIC "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 1.0//EN" "http://java.sun.com/dtd/jdo_1_0.dtd">

<jdo>
  <package name="samples.xdoclet">
    <class name="Main"
           identity-type="application"
           objectid-class="Main$Id"
    > <!-- end class tag -->
    <extension vendor-name="kodo"
               key="data-cache-timeout"
               value="10">
   </extension>
      <field name="pk1" 
             primary-key="true"
      > <!-- end field tag -->
    <extension vendor-name="kodo"
               key="jdbc-auto-increment"
               value="true">
   </extension>
      </field>
      <field name="pk2" 
             primary-key="true"
      > <!-- end field tag -->
    <extension vendor-name="kodo"
               key="jdbc-size"
               value="20">
   </extension>
      </field>
      <field name="name" 
             null-value="exception"
             default-fetch-group="false"
      > <!-- end field tag -->
      </field>
      <field name="nodes" 
             default-fetch-group="true"
      > <!-- end field tag -->
             <collection
                 element-type="samples.xdoclet.Main"
                 embedded-element="false"
              > <!-- end collection tag -->
             </collection>
    <extension vendor-name="kodo"
               key="inverse-owner"
               value="main">
   </extension>
      </field>
      <field name="cache" 
      > <!-- end field tag -->
              <map
                 key-type="java.lang.String"
                 value-type="java.lang.Integer"
              > <!-- end map tag -->
             </map>
      </field>
    </class>
  </package> 

    <!--
    To use additional vendor extensions, create a vendor-extensions.xml file that
    contains the additional extensions (in extension tags) and place it in your
    projects merge dir.  
    -->

</jdo>

A complete XDoclet sample is included in the samples/xdoclet directory of your Kodo distribution.