XDoclet for HibernateNote: This is a rough draft based on some notes detailing the pains of figuring out how to use XDoclet with Hibernate. This document reflects the current releases: Hibernate2 Beta3, and XDoclet 1.2 Beta3. Note: Add <hibernate version="2.0"/> to your Ant build if you're using the latest XDoclet and like to generate a Hibernate 2.0 conform mapping. You will get strange errors otherwise (Hibernate 1.x mappings don't work well with Hibernate2).
OverviewXDoclet (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 projet. This document is not a complete reference on the XDoclet component for Hibernate.
Getting XDoclet for your project1- Download the latest XDoclet release (Currently XDoclet1.2 Beta3) 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 script1- 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 discriptors, 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/>
</hibernatedoclet>
Note This will generate mappings for hibernate 1.x and it requires the version to be set for version 2.0 mappings to be generated, eg,
<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>
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 your haved 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. 4- Currently, XDoclet only generates Hibernate class descriptor files for Hibernate1. To overcome this obstacle, and generate Hibernate2 class descriptor files, insert the following task after your <hiberatedoclet> task:
<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>
This task essentially renames all instances of the read-only attribute to inverse, the role attribute to name, and updates the DTD definition to hibernate-mapping-2.0.dtd. /Of course if you are not using Hibernate2, then do not include this task.
Complete ExampleThe 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 -->
<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 code1- 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.
See Also
|