HIBERNATE JBoss.org
 |  Register  | 
     
News 
About 
   Feature List 
   Road Map 
Documentation 
   Related Projects 
   External Documentation 
Download 
Forum & Mailinglists 
Support & Training 
JIRA Issue Tracking
Wiki Community Area


Hibernate Public Training Courses


Get Hibernate in Action eBook!


JavaWorld 2003 Finalist


Jolt Award 2004 Winner
      
Documentation > Community Area > XDoclet for Hibernate

XDoclet for Hibernate

Note: This document reflects the current releases: Hibernate2.1.3, and XDoclet 1.2.

Note: Make sure that you add <hibernate version="2.0"/> to your Ant build if you're using the XDoclet 1.2 and would like to generate Hibernate 2.0 conformant mappings.

Overview

XDoclet (http://xdoclet.sourceforge.net) is a code generation engine with the goal of continuous integration. It uses custom JavaDoc-like tags to generate external resource files to support the main Java classes. XDoclet has mainly been used for the auto-generation of EJB descriptors (and related J2EE container technologies).

The goal of this document is to provide a walk-through on getting XDoclet for Hibernate incorporated into a project. This document is not a complete reference on the XDoclet component for Hibernate.

Getting XDoclet for your project

1- Download the latest XDoclet release (Currently XDoclet1.2) from the website (http://xdoclet.sourceforge.net). You can either get pre-compiled binaries, or compile from a source archive snap-shot.

2- You need to be using Apache's Ant Build System (http://ant.apache.org/). Enough said, why aren't you already using it???

3- From the XDoclet release you download you will need the following JARs made available to Ant:

  • xdoclet-/X.X.X/.jar
  • xdoclet-hibernate-module-/X.X.X/.jar
  • xdoclet-xjavadoc-/X.X.X/.jar
  • xdoclet-xdoclet-module-/X.X.X/.jar
  • log4j-/X.X.X/.jar
  • commons-collections-2.0.jar
  • commons-logging.jar

One solution to make these available to your Ant project is to include these under the /lib directory, in their own directory names /xdoclet (or something).

Calling XDoclet from your Ant build script

1- Create a <target> to perform the generation of Hibernate class descriptor files. For example:

<target name="generate" 
        description="Generates Hibernate class descriptor files."
        depends="compile">
   ...
</target>

The generation target should always depend on the compilation target to validate the Java classes before potentially sending the XDoclet parser in a loop.

Another approach for larger projects that need to generate more than just Hibernate class descriptors, is to structure the targets so that generation can be granular or complete. For example:

<target name="generate" 
        description="Runs all auto-generation tools."
        depends="generate.hibernate, generate.ejb, generate.javadocs">
   ...
</target>

<target name="generate.hibernate" 
        description="Generates Hibernate class descriptor files."
        depends="compile">
   ...
</target>

<target name="generate.ejb" 
        description="Generates EJB deployment descriptor files."
        depends="compile">
   ...
</target>

<target name="generate.javadocs" 
        description="Generates JavaDoc API files."
        depends="compile">
   ...
</target>

2- Define the <taskdef> for the <hibernatedoclet> in your Ant build script. for example:

<taskdef name="hibernatedoclet"
    classname="xdoclet.modules.hibernate.HibernateDocletTask">
    <classpath>
        <fileset dir="${lib.home}/xdoclet">
            <include name="*.jar"/>
        </fileset>
    </classpath>
</taskdef>

This example assumes that you have put your XDoclet required JARs in the directory /lib/xdoclet and that you have defined the property ${lib.home} to resolve to your /lib directory. On some systems, you may need to upgrade your copy of ant to get the taskdef to 'see' the Hibernate doclet.

An alternative and more robust solution might be:

<path id="xdoclet.classpath">
    <fileset dir="${lib.home}/xdoclet">
        <include name="*.jar"/>
    </fileset>
</path>

<taskdef name="hibernatedoclet"
    classname="xdoclet.modules.hibernate.HibernateDocletTask">
    <classpath refid="xdoclet.classpath"/>
</taskdef>

3- Add the <hibernatedoclet> task to the generatation target. For example:

<hibernatedoclet
    destdir="${generated.home}"
    excludedtags="@version,@author,@todo"
    force="${generated.forced}"
    mergedir="${generated.home}"
    verbose="false">

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

    <hibernate version="2.0"/>

</hibernatedoclet>

Note This will generate mappings for hibernate 2.0 and it requires the version attribute to be removed to generate Hibernate 1.x mappings.

This example assumes that the classes you wish to generate Hibernate class descriptors for are in a package names hibernate.

Additionally, this example assumes that you have defined the property ${src.home} to resolve to your source root, and the property ${generated.home} to where you would like Hibernate class descriptor files to be saved.

Finally, the attribute force is set to the property ${generate.force} this allows you to control whether you want XDoclet to always generate Hibernate class descriptor files (true), or only when classes have been updated (false).

Note Due to a bug in either the Hibernate XDoclet module or XDoclet iteself, the attribute mergedir must be the same as the attribute destdir or XDoclet will throw a nasty exception if the attribute force is false. The real bug is that the attribute force should use the path from the attribute destdir as it is independent of the merge feature.

Complete Example for Hibernate 1.0

The complete generation target would look as follows:

<target name="generate" 
        description="Generates Hibernate class descriptor files."
        depends="compile">

    <!-- Define the hibernatedoclet task -->
    <taskdef name="hibernatedoclet"
        classname="xdoclet.modules.hibernate.HibernateDocletTask">
        <classpath>
            <fileset dir="${lib.home}/xdoclet">
                <include name="*.jar"/>
            </fileset>
        </classpath>
    </taskdef>

    <!-- Execute the hibernatedoclet task -->
    <hibernatedoclet
        destdir="${generated.home}"
        excludedtags="@version,@author,@todo"
        force="${generated.forced}"
        mergedir="${generated.home}"
        verbose="false">
    
        <fileset dir="${src.home}">
            <include name="**/hibernate/*.java"/>
        </fileset>
    
        <hibernate/>
    
    </hibernatedoclet>

    <!-- Upgrade grammar from Hibernate1 to Hibernate2 - not needed if you are using the <hibernate version="2.0"/> tag -->
    <replace dir="${generated.home}">
        <include name="**/hibernate/*.hbm.xml"/>
        <replacefilter token="readonly=" value="inverse="/>
        <replacefilter token="role=" value="name="/>
        <replacefilter token="hibernate-mapping.dtd" value="hibernate-mapping-2.0.dtd"/>
    </replace>

</target>

Integrating XDoclet tags into your source code

1- Write your Hibernate classes. For example:

import java.util.Set;
import java.util.Collections;

public class Customer {

    private long id;

    private String name;

    private Set orders = Collections.EMPTY_SET;

    public Customer() {
    }

    public long getId() {
        return(id);
    }

    public void setId(long lId) {
        id = lId;
    }

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

    public void setName(String sName) {
        name = sName;
    }

    public Set getOrders() {
        return(orders);
    }

    public void setOrders(Set oOrders) {
        orders = oOrders;
    }
}

2- Add the XDoclet tags as part of your JavaDoc comments. The following example shows the minimum manditory tags for XDoclet (and Hibernate):

import java.util.Set;
import java.util.Collections;

/** A business entity class representing an Acme Company customer.
  * 
  * @author Bill Smith
  * @since 1.0
  * @hibernate.class  table="customers"
  */
public class Customer {

    /** This Customer's identifier field.
      */
    private long id;

    /** This Customer's name field.
      */
    private String name;

    /** The customer's orders set.
      */
    private Set orders = Collections.EMPTY_SET;

    /** The default construtor for Hibernate to instantiate with.
      */
    public Customer() {
    }

    /** The getter method for this Customer's identifier.
      *
      * @hibernate.id  generator-class="native"
      */
    public long getId() {
        return(id);
    }

    /** The setter method for this Customer's identifier.
      */
    public void setId(long lId) {
        id = lId;
    }

    /** The getter method for this Customer's name
      *
      * @hibernate.property
      */
    public String getName() {
        return(name);
    }

    /** The setter method for this Customer's name.
      */
    public void setName(String sName) {
        name = sName;
    }

    /** The getter method for this Customer's orders.
      *
      * @hibernate.set  role="orders"
      * 
      * @hibernate.collection-key  column="customer_id"
      * 
      * @hibernate.collection-one-to-many  class="Order"
      */
    public Set getOrders() {
        return(orders);
    }

    /** The setter method for this Customer's orders.
      */
    public void setOrders(Set oOrders) {
        orders = oOrders;
    }
}

3- Run this class through your Ant script to generate the following Hibernate class descriptor file (Note: The XML has been reformatted heavily):

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
    <class name="Customer" table="customers">
        
        <id name="id">
            <generator class="native"></generator>
        </id>

        <property name="name"
                  type="java.lang.String"
                  column="name"
                  not-null="false"
                  unique="false"/>
            
        <set name="orders"
             lazy="false"
             inverse="false"
             cascade="none"
             sort="unsorted">

            <key column="customer_id"></key>
            <one-to-many class="Order"/>

    </set>

    </class>
</hibernate-mapping>

4- Again, since XDoclet only generates Hibernate class descriptor files for Hibernate1. You will need to pay attention to the following for Hibernate2:

  • The tag role for @hibernate.collection, @hibernate.set, @hibernate.bag, @hibernate.map, @hibernate.list, @hibernate.array, and @hibernate.primitive-array will become the name attribute. Therefore the value for the role tag should therefore be exactly name of its property (i.e. "Orders" in the previous example). Unfortunately, this is a workaround to what should be picked up automatically by XDoclet.
  • The tag readonly for @hibernate.set, and @hibernate.bag will become the inverse attribute. Therefore treat readonly like the inverse attribute.
  • Note that XDoclet will not be able to support the new composite features.

Creating Hibernate Configuration File (hibernate.cfg.xml)

XDoclet only generates Hibernate class descriptor files, not the Hibernate configuration file (hibernate.cfg.xml). You may use the following BeanShell script to generate the file list automatically from Ant.

1- Add the following target to your Ant build.xml file. This will generate the file list (hbnfiles.xml):

  <target name="file-list"
          description="Create hibernate mapping file list">
    <property name="generated.dir" location="target/classes"/>
    <property name="hibernate.list.file" location="hbnfiles.xml"/>
    <script language="beanshell" src="create_hbnfiles.bsh"/>
  </target>

2- Create a configuration file (hibernate.cfg.xml) as follows. It will include the file list generated previously:

<?xml version="1.0"?>

<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 2.0//EN" 
    "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd" [
    <!ENTITY fileList SYSTEM "file:hbnfiles.xml">
]>

<hibernate-configuration>

  <session-factory
    name="java:comp/env/hibernate/SessionFactory">

    <!-- This will include a list of all the mapping files to
         be used for this configuration.
         -->
    &fileList;

  </session-factory>

</hibernate-configuration>

3- Create a BeanShell script named create_hbnfiles.bsh with the following code:

// This file contains a BeanShell script that
// works together with the Ant project file.
// This script searches for hibernate mapping
// files and writes the filenames out to a
// hibernate configuration file.

// Read properties configured in the Ant
// project file.
//
// This property tells us where to look for
// hibernate mapping files.
String basedirName = project.getProperty("generated.dir");
print("Search for hibernate files in " + basedirName);

// This property the filename to use when writing
// out the hibernate file list.
String outFilename = project.getProperty("hibernate.list.file");
print("Output to " + outFilename);

/**********************************************/

/*
 * Write a line to the configuration file.
 */
void dumpFilename(String s) {
  out.println("<mapping resource=\"" + s + "\"/>");
}

/**********************************************/
/*
 * Combine the relative paths together to
 * form a complete path.  If either is empty,
 * then there is no separator since they are
 * not really being combined.
 */
String relativeName(String base, String name) {
  if (base.length() == 0 || name.length() == 0)
    return base + name;
  else
    return base + "/" + name;
}

/**********************************************/
/*
 * This method reads the directory specified
 * and looks for file names to output to the
 * configuration file.  It invokes itself
 * recursively to find all files.
 */
void readDir(String relativePath) {
  // Combine relativePath with basedirName
  // to get get full path
  String fullName = relativeName(basedirName, relativePath);
  File dir = new File(fullName);

  // Get a listing of all directories
  // and all files ending in .hbm.xml
  //
  String[] list = dir.list(new FilenameFilter() {
    boolean accept(File dir, String name) {
      File f = new File(dir, name);
      if (f.isDirectory())
        return true;
      else if (f.isFile() && name.endsWith(".hbm.xml"))
    return true;
      else
        return false;
    }
  });

  count = 0;
  for (i=0; i<list.length; i++) {
    f = new File(fullName + "/" + list[i]);
    if (f.isFile()) {
      dumpFilename(relativeName(relativePath, f.getName()));
      count++;
    } else if (f.isDirectory()) {
      readDir(relativeName(relativePath, f.getName()));
    }
  }

  // Print out summary of what we found
  if (count > 0) {
    print("readDir: " + relativePath);
    print("Found Files: " + count);
  }
}

/**********************************************/
//                  MAIN

// Keep a backup copy of the current file.
mv("NoSuchFile", "hereNow");
mv(outFilename, outFilename+".bak");

File configFile = new File(outFilename);
PrintWriter out = new PrintWriter(new FileWriter(configFile));

// Kick things off by passing in an empty String.
//
readDir("");

// Close the config file we just created.
out.close();

4- BeanShell scripting is not included as a core task in Ant, so you will need to configure Ant to allow it. See http://www.beanshell.org/manual/bsf.html#Ant for instructions. It is recommended that you use Ant 1.6.1 or later.

See Also

      

coWiki