HyperJaxb Java Net

27
HyperJAXB - Relational persistence for JAXB Aleksei Valikov 1. Introduction Primary goal of this development is to provide JAXB objects with a relational persistence layer. To achieve this goal, we have developed an add-on that allows combining Sun's reference implementation of JAXB (XJC) with Hibernate, a well-known object/relational mapping tool. To map between relational tables and columns and Java objects, Hibernate requires an explicit definition of mapping, usually in a form of XML file. Having this mapping, Hibernate can save objects in a relational data- base, load them back, query for them etc. Consequently, to persist JAXB objects with Hibernate, we need to produce object/relational mapping for these objects. Instead of defining mappings in XML files, many Hibernate users prefer using XDoclet technology that allows to specify mapping properties directly in the source code of the object classes. In this case, source code is an- notated with special tags called "xdoclets". XDoclet parses source code and generates required artifacts (like Hibernate mapping). XDoclet allows developers to remain source-centric instead of supporting several related but separated artifacts (source code and mapping definitions) in parallel. XDoclet is a perfect point to bring JAXB and Hibernate together. On the one hand, JAXB implementations automatically generate source code of Java objects based on the XML Schema. On the other hand, XDoclet automatically generates object/relational mapping for Hibernate based on the annotated source code. The only missing fragment in this puzzle is automatic generation of xdoclets in JAXB objects. This missing fragment is implemented by the HyperJAXB package. HyperJAXB extends the code generated by Sun's reference implementation of JAXB with Hibernate xdoclet annotations. Annotated sources of JAXB objects are processed into object/relational mapping for Hibernate. Hibernate also provides tools to produce database schema for the target database from the generated mapping. A combination of JAXB RI, HyperJAXB and Hibernate tools allows automatically generating the following ar- tifacts: source code of JAXB objects with Hibernate XDoclet annotations; object/relational mapping for Hibernate; database schema for the target database. 2. Generating Hibernate xdoclets and other required con- structs 2.1. Basics In order to make JAXB objects persistable, we need to generate adequate object/relational mapping annotations 1

Transcript of HyperJaxb Java Net

Page 1: HyperJaxb Java Net

HyperJAXB - Relational persistence forJAXB

Aleksei Valikov

1. Introduction

Primary goal of this development is to provide JAXB objects with a relational persistence layer. To achieve thisgoal, we have developed an add-on that allows combining Sun's reference implementation of JAXB (XJC) withHibernate, a well-known object/relational mapping tool.

To map between relational tables and columns and Java objects, Hibernate requires an explicit definition ofmapping, usually in a form of XML file. Having this mapping, Hibernate can save objects in a relational data-base, load them back, query for them etc. Consequently, to persist JAXB objects with Hibernate, we need toproduce object/relational mapping for these objects.

Instead of defining mappings in XML files, many Hibernate users prefer using XDoclet technology that allowsto specify mapping properties directly in the source code of the object classes. In this case, source code is an-notated with special tags called "xdoclets". XDoclet parses source code and generates required artifacts (likeHibernate mapping). XDoclet allows developers to remain source-centric instead of supporting several relatedbut separated artifacts (source code and mapping definitions) in parallel.

XDoclet is a perfect point to bring JAXB and Hibernate together. On the one hand, JAXB implementationsautomatically generate source code of Java objects based on the XML Schema. On the other hand, XDocletautomatically generates object/relational mapping for Hibernate based on the annotated source code. The onlymissing fragment in this puzzle is automatic generation of xdoclets in JAXB objects. This missing fragment isimplemented by the HyperJAXB package.

HyperJAXB extends the code generated by Sun's reference implementation of JAXB with Hibernate xdocletannotations. Annotated sources of JAXB objects are processed into object/relational mapping for Hibernate.Hibernate also provides tools to produce database schema for the target database from the generated mapping.

A combination of JAXB RI, HyperJAXB and Hibernate tools allows automatically generating the following ar-tifacts:

• source code of JAXB objects with Hibernate XDoclet annotations;

• object/relational mapping for Hibernate;

• database schema for the target database.

2. Generating Hibernate xdoclets and other required con-structs

2.1. Basics

In order to make JAXB objects persistable, we need to generate adequate object/relational mapping annotations

1

Page 2: HyperJaxb Java Net

for all the stateful elements of the classes. This effectively means that we have to map all the properties of thegenerated implementation classes.

Depending on the cardinality, properties may be single or collection properties. Collection properties are thosereturning instances of java.util.Set, java.util.Map, java.util.List and so on.

Collections may be homogeneous (all elements have the same type) and heterogeneous (types of elements maydiffer). Although both options are used in JAXB, Hibernate generally discourages usage of heterogeneous col-lections. Therefore we introduce additional constructions to homogenize heterogeneous collections.

Collections or single properties may store simple or complex values. HyperJAXB supports five basic categoriesof values:

• simple values - primitives (int, float, boolean and so on) and basic object types (java.lang.String,java.lang.Boolean, java.math.BigDecimal etc.);

• persistent typesafe enums;

• "any" type;

• DOM elements;

• complex values - references to other composite objects.

The task of the add-on is to generate mapping definitions for properties of all of the types as well as other con-structs which are required for the mapping to work.

2.2. General constructs

2.2.1. Class-level xdoclets

Hibernate requires persistent classes to be explicitly declared. To achieve this, generated implementationclasses are annotated with @hibernate.class xdoclet. Name of the database table is explicitly specified in thetable attribute of this xdoclet.

Table name is derived from the name of the implemented interface. Derivation algorithm ensures that generatedname is unique across all classes of all packages. In future we plan to make name of the table customizable.

Since different packages may contain classes with equal names, auto-import must be turned off to avoid namecollission. This is achieved with auto-import="false" in @hibernate.mapping class-level xdoclet.

// ...package de.baw.nokis.impl;

/*** @hibernate.mapping auto-import="false"* @hibernate.class table="RespParty"*/

public class RespPartyImpl implements de.baw.nokis.RespParty // ...{

// ...}

XJC generates implementation classes for top-level elements as subclasses of implementation classes of theircomplex types. HyperJAXB follows "table-per-class" strategy in polymorphic mapping. Accordingly, imple-mentation classes for top level elements are annotated with @hibernate.joined-subclass and

HyperJAXB - Relational persistence for JAXB

2

Page 3: HyperJaxb Java Net

@hibernate.joined-subclass-key xdoclets. Joined subclass key column is always named parentid.

// ...package de.baw.nokis.impl;

/*** @hibernate.joined-subclass table="Party"* @hibernate.joined-subclass-key column="parentid"*/

public class PartyImpl extends de.baw.nokis.impl.RespPartyImplimplements de.baw.nokis.Party // ...

{// ...

}

2.2.1.1. Customization

You may customize @hibernate.class and @hibernate.joined-subclass xdoclets by providing your ownannotations in the binding customization. Consider the following example:

<xs:element name="metadata" type="iso19115:MD_Metadata"><xs:annotation>

<xs:appinfo><jaxb:class><jaxb:javadoc>@hyperjaxb.hibernate.joined-subclass table="MetadataElement"

</jaxb:javadoc></jaxb:class>

</xs:appinfo></xs:annotation>

</xs:element>

<xs:complexType name="MD_Metadata"><xs:annotation>

<xs:appinfo><jaxb:class><jaxb:javadoc>@hyperjaxb.hibernate.class table="Metadata"

</jaxb:javadoc></jaxb:class>

</xs:appinfo></xs:annotation><!-- ... -->

</xs:complexType>

These customization will map metadata element onto the MetadataElement table and MD_Metadata complextype onto the Metadata table.

/*** @hibernate.mapping auto-import="false"* @hibernate.class table="Metadata"*/

public class MDMetadataImpl implements MDMetadata // ...{ // ... }

/*** @hibernate.joined-subclass table="MetadataElement"* @hibernate.joined-subclass-key column="parentid"*/

public class MetadataImpl extends MDMetadataImpl // ...{ // ... }

This trick is especially helpful, if your classes have names prohibited in your database (like Table or User).

Please note that xdoclets you provide in customizing annotations replace xdoclets generated by HyperJAXB bydefault completely.

HyperJAXB - Relational persistence for JAXB

3

Page 4: HyperJaxb Java Net

2.2.1.2. Customizing the cache usage

You may also customize usage of the Hibernate cache (from simple test):

<xsd:complexType name="EntryType"><xsd:annotation><xsd:appinfo><jaxb:class>

<jaxb:javadoc>@hyperjaxb.hibernate.cache usage="read-write"

</jaxb:javadoc></jaxb:class>

</xsd:appinfo></xsd:annotation><!-- ... -->

</xsd:complexType>

2.2.2. Identifier property

Hibernate requires that each persistent class declares a primary key column of the database table. To achievethis, each implementing class (except for the subclasses) is extended with an identifier property. This propertyis defined by the string idInternal field and a getter/setter pair getIdInternal()/setIdInternal(String an-

Id). Identifier is a 32-character string assigned by the Hibernate when object is saved into the database for thefirst time. To allow Hibernate distinguish new instances, unsaved-value="null" attribute is generated as well.

// ...package de.baw.nokis.impl;

// ..public class ContactImpl implements de.baw.nokis.Contact // ...{

private java.lang.String idInternal;

/*** @hibernate.id type="string" unsaved-value="null" length="32" generator-class="uuid.hex"*/public java.lang.String getIdInternal(){return idinternal;

}

public void setIdInternal(java.lang.String anId){idInternal = anId;

}

// ...}

2.2.2.1. Customizing the identifier property

Identifier property is customizable. Instead of generating a new idInternal property, you may force Hyper-JAXB to use an existing property as identifier. To do this, include @hyperjaxb.hibernate.id into the propertyjavadoc annotation (from the simple test):

<xsd:complexType name="EntryType"><xsd:sequence><!-- ... --></xsd:sequence><xsd:attribute name="id" type="xsd:string"><xsd:annotation><xsd:appinfo>

<jaxb:property><jaxb:javadoc>@hyperjaxb.hibernate.id unsaved-value="null" generator-class="uuid.hex"

HyperJAXB - Relational persistence for JAXB

4

Page 5: HyperJaxb Java Net

</jaxb:javadoc></jaxb:property>

</xsd:appinfo></xsd:annotation>

</xsd:attribute></xsd:complexType>

If you use an id generator that needs to be parametrized, include the required parameters [email protected].

2.3. Mapping properties

2.3.1. Single properties

2.3.1.1. Simple properties

Simple properties of JAXB objects are annotated with @hibernate.property xdoclet:

/*** @hibernate.property*/

public java.lang.String getPostCode(){

return _PostCode;}

2.3.1.1.1. Customizing simple single properties

To customize simple single property, include a @hyperjaxb.hibernate.property xdoclet into the propertyjavadoc (from the simple test):

<xsd:complexType name="EntryType"><!-- ... --><xsd:sequence><!-- ... --><xsd:element name="value" type="xsd:string"><xsd:annotation>

<xsd:appinfo><jaxb:property><jaxb:javadoc>

@hyperjaxb.hibernate.property length="12"</jaxb:javadoc>

</jaxb:property></xsd:appinfo>

</xsd:annotation></xsd:element><!-- ... -->

</xsd:sequence><!-- ... -->

</xsd:complexType>

2.3.1.2. Simple binary properties

There is a special case of binary simple properties: for XML Schema types like base64Binary JAXB generatesbyte[]-typed properties. To support persistance of binary properties, HyperJAXB uses Hibernate'snet.sf.hibernate.type.BinaryType.

/*** @hibernate.property type="net.sf.hibernate.type.BinaryType"*/

public byte[] getValue()

HyperJAXB - Relational persistence for JAXB

5

Page 6: HyperJaxb Java Net

{return _Value;

}

2.3.1.3. Enum properties

As an extension, JAXB supports typesafe enum model for the enumerated simple types. Consider the followingschema fragment:

<xsd:annotation><xsd:appinfo><jaxb:globalBindings typesafeEnumBase="test:SexType"/>

</xsd:appinfo></xsd:annotation>

<xsd:simpleType name="SexType"><xsd:restriction base="xsd:string"><xsd:enumeration value="Male"/><xsd:enumeration value="Female"/>

</xsd:restriction></xsd:simpleType>

For the enumerated simple type SexType from the fragment above JAXB generates the following class:

// ...package test;

/*** Java content class for SexType.* <p>The following schema fragment specifies the expected content contained within this java content object.* <p>* <pre>* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string">* &lt;enumeration value="Male"/>* &lt;enumeration value="Female"/>* &lt;/restriction>* </pre>*/

public class SexType implements java.io.Serializable{

private final static java.util.Map valueMap = new java.util.HashMap();public final static java.lang.String _MALE = com.sun.xml.bind.DatatypeConverterImpl.installHook("Male");public final static test.SexType MALE = new test.SexType(_MALE);public final static java.lang.String _FEMALE = com.sun.xml.bind.DatatypeConverterImpl.installHook("Female");public final static test.SexType FEMALE = new test.SexType(_FEMALE);private final java.lang.String lexicalValue;private final java.lang.String value;

protected SexType(java.lang.String v) {value = v;lexicalValue = v;valueMap.put(v, this);

}

public java.lang.String toString() {return lexicalValue;

}

public java.lang.String getValue() {return value;

}

public final int hashCode() {return super.hashCode();

}

public final boolean equals(java.lang.Object o) {

HyperJAXB - Relational persistence for JAXB

6

Page 7: HyperJaxb Java Net

return super.equals(o);}

public static test.SexType fromValue(java.lang.String value) {test.SexType t = ((test.SexType) valueMap.get(value));if (t == null) {

throw new java.lang.IllegalArgumentException();} else {

return t;}

}

public static test.SexType fromString(java.lang.String str) {return fromValue(str);

}}

HyperJAXB supports persistance of typesafe enums with the help of user types (seenet.sf.hibernate.UserType). Each of the typesafe enum classes is appended with an inner static class Type:

// ...

package test;

// ...

public class SexType implements java.io.Serializable{

// ...public static class Type implements net.sf.hibernate.UserType, java.io.Serializable{

public int[] sqlTypes() {return new int[] {java.sql.Types.VARCHAR };

}

public java.lang.Class returnedClass() {return test.SexType.class;

}

public boolean equals(java.lang.Object x, java.lang.Object y) {return (x == y);

}

public java.lang.Object deepCopy(java.lang.Object value) {return value;

}

public boolean isMutable() {return false;

}

public java.lang.Object nullSafeGet(java.sql.ResultSet resultSet, java.lang.String[] names, java.lang.Object owner)throws java.sql.SQLException, net.sf.hibernate.HibernateException

{java.lang.String name = resultSet.getString(names[ 0 ]);return (resultSet.wasNull()?null:test.SexType.fromString(name));

}

public void nullSafeSet(java.sql.PreparedStatement statement, java.lang.Object value, int index)throws java.sql.SQLException, net.sf.hibernate.HibernateException

{if (value == null) {

statement.setNull(index, java.sql.Types.VARCHAR);} else {

statement.setString(index, value.toString());}

}}

}

HyperJAXB - Relational persistence for JAXB

7

Page 8: HyperJaxb Java Net

Generated user type class is specified as the type of the property:

/*** @hibernate.property type="test.SexType$Type"*/

public test.SexType getSex(){return _Sex;

}

2.3.1.4. DOM properties

As a vendor extension, XJC supports xjc:dom customization, which allows you to map a certain parts of theschema into a DOM tree. This is very helpful when you deal with semi-structured content or large externalschemas that you don't want to process structurally.

For xjc:dom-annotated constructs, XJC generates properties with types from DOM hierarchy.

HyperJAXB supports persistance of DOM properties with the help of a user typede.fzi.dbs.jaxb.addon.hibernate.ElementType. This special user type serializes DOM content when sav-ing and parses the XML back when loading data from the database.

/*** @hibernate.property type="de.fzi.dbs.jaxb.addon.hibernate.ElementType"*/

public org.w3c.dom.Element getMath(){return _Math;

}

2.3.1.5. Properties of "any" type

The type "any" is a very powerful XML Schema construct. It adds great flexibility to XML Schemas. On theobject level, this construct is represented by the heterogeneous association. Although this type of association isnot directly supported by the relational model, Hibernate still allows to map it using any mapping.

Consider the following schema fragment:

<xsd:element name="root" type="test:RootType"/>

<xsd:complexType name="RootType"><xsd:sequence><xsd:any processContents="strict"/>

</xsd:sequence></xsd:complexType>

The element root may contain any other declared element. To map "any" construct, the corresponding propertyis annotated with @hibernate.any and @hibernate.any-column xdoclets:

/*** @hibernate.any id-type="string" cascade="all"* @hibernate.any-column name="Any_class"* @hibernate.any-column name="Any_id" length="32"*/

public java.lang.Object getAny(){

return _Any;}

2.3.1.6. Complex properties

HyperJAXB - Relational persistence for JAXB

8

Page 9: HyperJaxb Java Net

Complex properties are essentially one-to-one associations which may be implemented in Hibernate withprimary key or foreign key constraints. Primary key associations require related objects to have equal identifi-ers. This does not suit well our identifier model (each object has its own globally unique identifier). Therefore,one-to-one associations are implemented with foreign keys. This corresponds to the many-to-one mapping:

/*** @hibernate.many-to-one cascade="all"* class="de.baw.nokis.impl.RespPartyRecImpl"*/

public de.baw.nokis.RespPartyRec getRespPartyRec(){

return _RespPartyRec;}

2.3.2. Collection properties

2.3.2.1. Proxies for collection properties

During the early implementation phases of HyperJAXB we have found out that JAXB collection propertiescannot be persisted with Hibernate directly. This is due to a number of reasons. First of all, Hibernate requiresall persistent properties (including persistent collection properties) to have setters, while JAXB generates nosetters for collections. Second, XJC exposes collections wrapped in proxy classes:

protected com.sun.xml.bind.util.ListImpl _Value =new com.sun.xml.bind.util.ListImpl(new ArrayList());

public java.util.List getValue(){

return _Value;}

At the same time Hibernate checks identity of collection properties by object identity. Proxied field makes Hi-bernate think that a new collection was assigned to the property. This results in full rewriting of the collectionwhen object is saved or loaded.

To solve this and few other problems, we generate a supporting inner property for each of the collection proper-ties. Main property is exposed as proxy of the inner property. Inner property has both getter and setter accessingthe field directly and may be persisted by Hibernate. For example, the value collection property in the listingabove will be augmented as follows:

protected com.sun.xml.bind.util.ListImpl _Value =new com.sun.xml.bind.util.ListImpl(new test.impl.ElementTypeImpl.ValueInternalProxyList());

private java.util.List _ValueInternal = new java.util.ArrayList();

public java.util.List getValue(){

return _Value;}

public java.util.List getValueInternal(){

return _ValueInternal;}

public void setValueInternal(java.util.List theValueInternal){

_ValueInternal = theValueInternal;}

public class ValueInternalProxyList extends java.util.AbstractList{

HyperJAXB - Relational persistence for JAXB

9

Page 10: HyperJaxb Java Net

public java.lang.Object get(int index){return _ValueInternal.get(index);

}

public java.lang.Object set(int index, java.lang.Object o){return _ValueInternal.set(index, o);

}

public void add(int index, java.lang.Object o){_ValueInternal.add(index, o);

}

public java.lang.Object remove(int index){return _ValueInternal.remove(index);

}

public int size(){return _ValueInternal.size();

}}

In the code above, _ValueInternal is the real field that will hold the real collection object. Accessors get-

ValueInternal() and setValueInternal(...) provide direct access to this field and may be used by Hibern-ate to persist the collection. The field _Value holds a JAXB proxy of the inner class ValueInternalProxyList,which provides access to the _ValueInternal field.

Supporting inner properties are also used to homogenize heterogeneous collections (this usage is discussedlater).

2.3.2.2. Simple collections

Simple collections are implemented by Hibernate's "collections of values", which are mapped as follows:

/*** @hibernate.list table="ContactImpl_FaxNumInternal" cascade="all" where="FaxNumInternal_index is not null"* @hibernate.collection-key column="ContactImpl_id"* @hibernate.collection-index column="FaxNumInternal_index"* @hibernate.collection-element column="value" type="java.lang.String"*/

public java.util.List getFaxNumInternal(){

return _FaxNumInternal;}

Collection key column is given the name classname_id, index column is propertyname_index, value columnis always named value. Note also where="propertyname_index is not null" condition which allows distin-guishing values belonging to different collections properties.

Binary collection implementations is currently under development.

2.3.2.3. Collection of typesafe enums

Collections of typesafe enums are mapped similarly to simple collections except for their type, which is set tothe generated user type.

/*** @hibernate.list table="RootType_SexesInternal" cascade="all" where="SexesInternal_index is not null"

HyperJAXB - Relational persistence for JAXB

10

Page 11: HyperJaxb Java Net

* @hibernate.collection-key column="RootTypeImpl_id"* @hibernate.collection-index column="SexesInternal_index"* @hibernate.collection-element column="value" type="test.SexType$Type"*/

public java.util.List getSexesInternal(){

return _SexesInternal;}

2.3.2.4. Collection of DOM elements

Collections of DOM elements are mapped just like simple collections with the exception that element type isset to de.fzi.dbs.jaxb.addon.hibernate.ElementType.

XML Schema fragment:

<xsd:complexType name="RootType"><xsd:sequence><!-- ... --><xsd:element name="contents" maxOccurs="unbounded"><xsd:annotation>

<xsd:appinfo><xjc:dom/>

</xsd:appinfo></xsd:annotation>

</xsd:element></xsd:sequence>

</xsd:complexType>

Augmented code:

/*** @hibernate.list table="RootType_ContentsInternal" cascade="all" where="ContentsInternal_index is not null"* @hibernate.collection-key column="RootTypeImpl_id"* @hibernate.collection-index column="ContentsInternal_index"* @hibernate.collection-element column="value" type="de.fzi.dbs.jaxb.addon.hibernate.ElementType"*/

public java.util.List getContentsInternal(){

return _ContentsInternal;}

2.3.2.5. Collection of "any" constructs

Collection of "any" constructs is mapped using many-to-any.

XML Schema fragment:

<xsd:complexType name="GroupType"><xsd:complexContent><xsd:extension base="test:Principal"><xsd:sequence>

<xsd:any maxOccurs="unbounded" processContents="strict"/></xsd:sequence>

</xsd:extension></xsd:complexContent>

</xsd:complexType>

Augmented code:

/*** @hibernate.list table="GroupType_AnyInternal" cascade="all" where="AnyInternal_index is not null"* @hibernate.collection-key column="GroupTypeImpl_id"

HyperJAXB - Relational persistence for JAXB

11

Page 12: HyperJaxb Java Net

* @hibernate.collection-index column="AnyInternal_index"* @hibernate.many-to-any id-type="string"* @hibernate.many-to-any-column name="AnyInternal_class"* @hibernate.many-to-any-column name="AnyInternal_id" length="32"*/

public java.util.List getAnyInternal(){

return _AnyInternal;}

2.3.2.6. Complex collections

Complex collections are implemented by one-to-many mappings.

/*** @hibernate.list cascade="all" where="CntOnlineResInternal_index is not null"* @hibernate.collection-key column="ContactImpl_id"* @hibernate.collection-index column="CntOnlineResInternal_index"* @hibernate.collection-one-to-many class="de.baw.nokis.impl.OnLineResImpl"*/

public java.util.List getCntOnlineResInternal(){

return _CntOnlineResInternal;}

Collection key column is given the name classname_id, index column is named propertyname_index.

2.3.2.7. Heterogeneous collections

Heterogeneous collections are collections holding more than one type of elements. In JAXB, these collectionsare produced for repeatable sequences or choices. Consider the following schema type:

<xsd:complexType name="RootType"><xsd:sequence maxOccurs="unbounded"><xsd:element name="element1" type="test:ElementType"/><xsd:element name="element2" type="test:ElementType"/>

</xsd:sequence></xsd:complexType>

This type will be represented by the following interface:

public interface RootType {/*** Objects of the following type(s) are allowed in the list* {@link RootType.Element1}* {@link RootType.Element2}*/java.util.List getElement1AndElement2();

public interface Element1 extends javax.xml.bind.Element, ElementType {}public interface Element2 extends javax.xml.bind.Element, ElementType {}

}

The getter getElement1AndElement2() exposes a heterogeneous collection. Although Hibernate can map het-erogeneous collection properties like this one using many-to-any mapping, this approach is not recommended.However, heterogeneous collections may be homogenized by introducing a new aggregating type:

public interface Element1AndElement2 {

public Element2 getElement2();public void setElement2(Element2 theElement2);public Element1 getElement1();public void setElement1(Element1 theElement1);

HyperJAXB - Relational persistence for JAXB

12

Page 13: HyperJaxb Java Net

}

This type has one property per type allowed in the heterogeneous collection. It is obvious that for any hetero-geneous collection storing Element1 or Element2 instances there is an equivalent homogeneous collection stor-ing instances of Element1AndElement2. This approach is used in HyperJAXB to generate supporting innerproperties as homogeneous collections. Inner collection is automatically converted into a heterogeneous collec-tion when it is exposed by the proxy list class.

2.3.2.8. Customizing collections

Mapping of the collection properties can be customized. To do this, include a @hyperjaxb.hibernate.list

xdoclet into the corresponding property javadoc annotation. If this xdoclet is found, HyperJAXB will generateno collection property mappings by its own. Instead, HyperJAXB will copy all the corresponding xdoclets fromthe annotation. To be more specific, these xdoclets are:

• @hyperjaxb.hibernate.list

• @hyperjaxb.hibernate.collection-cache

• @hyperjaxb.hibernate.collection-jcs-cache

• @hyperjaxb.hibernate.collection-key

• @hyperjaxb.hibernate.collection-key-column

• @hyperjaxb.hibernate.collection-index

• @hyperjaxb.hibernate.collection-element

• @hyperjaxb.hibernate.collection-composite-element

• @hyperjaxb.hibernate.collection-one-to-many

• @hyperjaxb.hibernate.index-many-to-many

• @hyperjaxb.hibernate.many-to-many

• @hyperjaxb.hibernate.many-to-any

• @hyperjaxb.hibernate.many-to-any-column

Please note that you will need to take care of table names, class names, element names etc. by yourself.

As an example, we will demonstrate customization of the Address collection withing User complex type:

<xs:complexType name="User"><xs:sequence><xs:element name="Address" type="pp:Address" maxOccurs="unbounded"><xs:annotation>

<xs:appinfo><jaxb:property><jaxb:javadoc>

@hyperjaxb.hibernate.listtable="address" cascade="all" where="address_id is not null"

@hyperjaxb.hibernate.collection-keycolumn="user_id"

@hyperjaxb.hibernate.collection-indexcolumn="address_id"

HyperJAXB - Relational persistence for JAXB

13

Page 14: HyperJaxb Java Net

@hyperjaxb.hibernate.collection-one-to-manycascade="all"class="com.pps.schema.impl.AddressImpl"

</jaxb:javadoc></jaxb:property>

</xs:appinfo></xs:annotation>

</xs:element><!-- ... -->

</xs:sequence><!-- ... -->

</xs:complexType>

This customization results in the following comment on the getAddressInternal() method:

/*** @hibernate.list table="address" where="address_id is not null" cascade="all"* @hibernate.collection-key column="user_id"* @hibernate.collection-index column="address_id"* @hibernate.collection-one-to-many class="com.pps.schema.impl.AddressImpl" cascade="all"*/

public java.util.List getAddressInternal() {return _AddressInternal;

}

And, finally, the Hibernate mapping of this collection is as follows:

<listname="addressInternal"table="address"lazy="false"inverse="false"cascade="all"where="address_id is not null">

<key column="user_id"/>

<index column="address_id"/>

<one-to-many class="com.pps.schema.impl.AddressImpl"/>

</list>

2.3.3. Ignoring properties

You may instruct HyperJAXB to ignore the certain property. To do this, mark this property [email protected] (from simple test):

<xsd:complexType name="ElementType"><xsd:sequence><!-- ... --><xsd:element name="ignored" type="xsd:string" minOccurs="0"><xsd:annotation>

<xsd:appinfo><jaxb:property><jaxb:javadoc>

@hyperjaxb.ignore</jaxb:javadoc>

</jaxb:property></xsd:appinfo>

</xsd:annotation></xsd:element><!-- ... -->

</xsd:sequence></xsd:complexType>

HyperJAXB - Relational persistence for JAXB

14

Page 15: HyperJaxb Java Net

HyperJAXB will generate no xdoclets for the ignored properties. Use this customization if you don't want a cer-tain property to be persisted.

3. Using HyperJAXB with XJC

This section describes the following aspects of using HyperJAXB:

• generating annotated JAXB classes;

• building generated classes;

• generating the Hibernate mapping;

• generating the database schema.

3.1. Using XJC and HyperJAXB to generate annotated class

To use HyperJAXB with XJC you need to include HyperJAXB library (hyperjaxb.jar) into the class path andturn on the add-on. Add-on is turned on using arg elements. A sample build file fragment is presented below:

<target name="generate.sources"><taskdef name="xjc"classname="com.sun.tools.xjc.XJCTask"classpathref="xjc.lib.path"/>

<mkdir dir="${generated.sources}"/><xjc target="${generated.sources}"><arg line="-nv"/><arg line="-extension"/><arg line="-Xhibernate-xdoclets"/><binding dir="${basedir}"><include name="binding/*.xml"/>

</binding><schema dir="${basedir}"><include name="schema/*.xsd"/>

</schema></xjc>

</target>

It is assumed that xjc.lib.path contains all the libraries required by XJC as well as hyperjaxb.jar and hi-

bernate2.jar.

After this step the generated.sources folder should contain generated sources annotated with Hibernatexdoclets.

3.2. Building generated classes

Building generated classes is not much different from building normal Java classes. However, sometimes itmakes sense to keep generated sources separate. If this is the case you'll need to build both generated andmanually written sources together using a target like:

<target name="compile" depends="generate.sources"><mkdir dir="${classes}"/><javac destdir="${classes}" debug="true"srcdir="${generated.sources}"classpathref="compile.lib.path">

</javac><copy todir="${classes}">

HyperJAXB - Relational persistence for JAXB

15

Page 16: HyperJaxb Java Net

<fileset dir="${generated.sources}"><exclude name="**/*.java"/>

</fileset></copy>

</target>

<target name="jar" depends="compile"><mkdir dir="${test.lib.dir}"/><jar jarfile="${test.lib.dir}/${jar.name}.jar" basedir="${classes}"/>

</target>

In this target, compile.lib.path must include XJC libraries (HyperJAXB library is not required). We alsoneed to copy resources with the copy task.

3.3. Generating the Hibernate mapping

Hibernate mapping is generated using the Hibernate XDoclet module. You'll first need to define the task andthen use it to produce the mapping. Here is the sample ant target:

<target name="generate.hibernate.mapping" depends="jar">

<taskdef name="hibernatedoclet"classname="xdoclet.modules.hibernate.HibernateDocletTask"classpathref="hibernatedoclet.lib.path"/>

<tstamp><format property="TODAY" pattern="dd-MM-yy"/>

</tstamp>

<mkdir dir="${hibernate.mapping}"/>

<hibernatedocletdestdir="${hibernate.mapping}"mergedir="${generated.sources}"excludedtags="@version,@author,@todo,@see,@throws"addedtags="@xdoclet-generated at ${TODAY},@copyright The XDoclet Team,@author XDoclet"force="false"verbose="false">

<fileset dir="${generated.sources}"><include name="**/*.java"/><exclude name="**/runtime/*.java"/>

</fileset>

<hibernate version="2.0"/></hibernatedoclet>

</target>

The path hibernatedoclet.lib.path in this target must include the whole set of Hibernate XDoclet libraries.Please consult the Hibernate documentation for a detailed listing.

After this step you should receive a number of Hibernate mapping files (???.hbm.xml) in your hibernate dir-ectory.

3.4. Generating the database schema

After the Hibernate mapping is generated it may be used to produce a database schema for the target database.

<target name="export.database.schema" depends="generate.hibernate.mapping">

<taskdef name="schemaexport"classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"

HyperJAXB - Relational persistence for JAXB

16

Page 17: HyperJaxb Java Net

classpathref="schemaexport.lib.path"/>

<mkdir dir="${database}"/>

<schemaexportproperties="${hibernate.properties}"quiet="yes"text="no"drop="no"delimiter=";"output="${database}/schema.sql"><fileset dir="${hibernate.mapping}"><include name="**/*.hbm.xml"/>

</fileset></schemaexport>

</target>

This target requires Hibernate properties defined in hibernate.properties file and ???.hbm.xml mappingfiles in hibernate directory. Output is the database schema in database/schema.sql. If text attribute is set tono (not text only), this task will also initialize the database defined in Hibernate properties.

4. Persisting JAXB objects with Hibernate

In this section we will consider two target usage scenarios: saving an unmarshalled JAXB object into the data-base and marshalling an object loaded from the database.

4.1. Prerequisites

4.1.1. Setting up a Hibernate session factory

Before you can use Hibernate to persist your objects, you'll need to set up a session factory. This basicallymeans that you'll need to load Hibernate properties and mapping files. The following code illustrates the factorysetup process.

/*** Test setup.** @throws Exception In case of setup problems.*/

public void setUp() throws Exception{

super.setUp();final Configuration cfg = new Configuration();

final Properties properties = new Properties();properties.load(new FileInputStream(getHibernatePropertiesFile()));cfg.setProperties(properties);addDirectory(cfg, getHibernateDirectory(), true, new DefaultFilenameFilter("*.hbm.xml"));

sessionFactory = cfg.buildSessionFactory();}

private Configuration addDirectory(final Configuration configuration,final File directory, final boolean recurse, final FilenameFilter filenameFilter)throws IOException, MappingException

{Configuration extendedConfiguration = configuration;if (!directory.isDirectory()){throw new IOException("Passed file handle [" +directory.getAbsolutePath() + "] is not a directory.");

HyperJAXB - Relational persistence for JAXB

17

Page 18: HyperJaxb Java Net

}final File[] files = directory.listFiles();for (int index = 0; index < files.length; index++){final File file = files[index];if (recurse && file.isDirectory()){extendedConfiguration = addDirectory(extendedConfiguration,

file, recurse, filenameFilter);}else if (file.isFile() &&filenameFilter.accept(directory, file.getName()))

{extendedConfiguration = extendedConfiguration.addFile(file);

}}return configuration;

}

/*** Returns the directory containing Hibernate mapping.* @return Directory containing Hibernate mapping.*/

public File getHibernateDirectory(){

return new File("hibernate");}

/*** Returns Hibernate properties file.* @return Hibernate properties file.*/

public File getHibernatePropertiesFile(){

return new File(getHibernateDirectory(), "hibernate.properties");}

The code constructs a new factory configuration, loads properties from the hibernate.properties file andadds all the mapping files (*.hbm.xml) recursively processing the hibernate directory and all its sub-directories. The loaded configuration is then used to produce a Hibernate session factory.

4.1.2. Setting up JAXB context, marshaller, unmarshaller and validator

For the instances of unmarshaller, marshaller and validator used to work with JAXB objects we first need toobtain a JAXB context:

final JAXBContext context = JAXBContext.newInstance("my.package.name");

unmarshaller = context.createUnmarshaller();marshaller = context.createMarshaller();validator = context.createValidator();

Instead of hardcoding the package name you could look it up dynamically using one of the generated classes:

final JAXBContext context = JAXBContext.newInstance(MyClass.class.getPackage().getName());

4.2. Unmarshall and saving

Having configured session factory and unmarshaller, you may now unmarshall the XML content into an objectand save this object into the database:

// Unmarshall the documentfinal RespParty respParty = (RespParty) unmarshaller.unmarshal(document);

HyperJAXB - Relational persistence for JAXB

18

Page 19: HyperJaxb Java Net

// Open the session, save object into the databasefinal Session saveSession = sessionFactory.openSession();// Save id for the later usefinal String id = saveSession.save(respParty);saveSession.flush();// Close the sessionsaveSession.close()

The code above assumes document is a DOM document you want to unmarshall to receive an instance of a re-sponsible party object (a RespParty instance).

4.3. Loading and marshalling

The loading and marshalling process is exactly the reverse of unmarshalling and saving. You look up the objectusing its class and identifier (note that you'll need the implementation class, not the interface class to look up)and utilize a marshaller to serialize it to DOM, SAX or any othe supported representation of XML.

// Open the session, load the objectfinal Session loadSession = sessionFactory.openSession();final RespParty loadedRespParty = (RespParty) loadSession.load(RespPartyImpl.class, id);loadSession.close();

// Marshall loaded object into the documentfinal Document loadedDocument = documentBuilder.newDocument();marshaller.marshal(loadedRespParty, loadedDocument);

The document loadedDocument should be identical to the document that we've unmarshalled and saved in theprevious section (modulo whitespace).

5. Project template

The easiest way to setup your project with HyperJAXB, is to use the prepared project template, included in thedistribution (dist/template.zip). For a basic setup you will only need to extract the template.zip into a newdirectory and place the schema into the schema folder.

The template project has the following directory structure:

• classes (generated during the build) - directory containing the compiled classes;

• database (generated during the build) - database directory;

• database/schema.sql (generated during the build) - database schema DDL;

• dist - distribution directory will contain the compiled and packed classes in a jar file;

• generated.source (generated during the build) - directory containing sources generated by JAXB and Hy-perJAXB;

• hibernate - contains Hibernate files;

• hibernate/hibernate.properties - Hibernate properties;

• hibernate/mapping (generated during the build) - Hibernate mapping files (*.hbm.xml);

• lib - library directory (libraries are placed in their own subdirectories, ex. commons-lang.jar in lib/

HyperJAXB - Relational persistence for JAXB

19

Page 20: HyperJaxb Java Net

jakarta-commons directory);

• log4j - Log4J directory;

• log4j/log4j.properties - Log4J configuration;

• schema - schema directory (the build assumes that schema files have *.xsd extension;

• src - non-generated source files.

To configure your HyperJAXB project, you'll need to edit the following files:

• build.xml

• text property - set to yes if you only want to generate database schema DDL without exporting it intothe database (default), specify no if you want to export the schema into the database;

• name property - specify the name of the project (default is template);

• compile.lib.path path - modify to add the required libraries;

• hibernate/hibernate.properties - set database properties used by Hibernate to generate mapping anddatabase schema;

• log4j/log4j.properties - configuration of logs used during the generation process.

Template build defines the following targets:

• init - initializes before the build (does noting in the template build);

• clean - cleans before the build (in the template build, deletes classes, generated.sources, hibernate/map-ping and database directories);

• generate.sources - invokes XJC and HyperJAXB to generate annotated sources in the gener-

ated.sources directory;

• compile (depends on the generate.sources target) - compiles generated and static (those from the src dir-ectory) sources into the classes directory;

• jar (depends on the compile target) - packs the compiled classes and places the generated jar file into thedist directory;

• generate.hibernate.mapping (depends on the jar target) - generates Hibernate mapping in the hibern-

ate/mapping directory;

• export.database.schema (depends on the generate.hibernate.mapping target) - exports databaseschema into the DDL file database/schema.sql and, if text property is set to no, also into the databasespecified in the hibernate/hibernate.properties file;

• all (depends on the export.database.schema target) - full build process (default target).

6. Sample application

HyperJAXB - Relational persistence for JAXB

20

Page 21: HyperJaxb Java Net

HyperJAXB distribution contains a number of test projects in the tests directory. In this section we will con-sider one of these examples (iso19115) in detail.

The sample application we will examine demonstrates a full XML-objects-database roundtrip:

• XML document (initial document) is loaded from file and unmarshalled into a JAXB object;

• the object is saved into the database;

• the object is loaded from the database (in a new session);

• loaded object is marshalled into an XML document (final document);

• initial and final documents are compared for identity.

To support this roundtrip we use XJC and HyperJAXB to generate annotated JAXB classes. Generated sourcesare then utilized to produce Hibernate mapping and database schema.

To build and run the application simply run runtest iso19115 in the tests directory. This will generate JAXBobjects out of the schema, produce Hibernate mapping, database schema and finally run the roundtrip test case.You will not need to set up the database. Example runs against an embedded in-memory HSQL database thatdoes not require any setup.

6.1. The schema

The starting point is an XML schema stored in tests/iso19115/schema/schema.xsd. This schema is a verysmall sample schema based on the ISO standard for geographic metadata.

HyperJAXB - Relational persistence for JAXB

21

Page 22: HyperJaxb Java Net

6.2. Generated artifacts

During the build process, Ant generates the following artifacts:

• sources of JAXB objects in (with Hibernate xdoclets) generated.sources;

• compiled and packed classes of the projects in lib/test.jar;

• Hibernate mappings in the hibernate/mapping;

• database schema in the database/schema.sql.

The generated classes implement the following content interface structure:

HyperJAXB - Relational persistence for JAXB

22

Page 23: HyperJaxb Java Net

The generated database schema has the following structure:

HyperJAXB - Relational persistence for JAXB

23

Page 24: HyperJaxb Java Net

6.3. Roundtrip test case

The roundtrip test case (de.fzi.dbs.jaxb.tests.RoundtripTestCase) unmarshalls a sample XML document,saves unmarshalled object into the database, loads it back and marshalls again into XML. What we need is totest the identity of initial and final XML modulo whitespace. This is done with the following test method:

/*** Tests document roundtrim: unmarshall into an object, save object into the database,* load object from the database, marshall the object.* Resulting XML should be equivalent to the initial XML.*/

public void testRoundtrip(){

try{// Unmarshall the documentfinal RespParty respParty = (RespParty) unmarshaller.unmarshal(document);// Open the session, save object into the databasefinal Session saveSession = sessionFactory.openSession();// Save id for the later usefinal String id = (String) saveSession.save(respParty);saveSession.flush();// Close the sessionsaveSession.close();

// Open the session, load the objectfinal Session loadSession = sessionFactory.openSession();final RespParty loadedRespParty = (RespParty) loadSession.load(RespPartyImpl.class, id);loadSession.close();// Marshall loaded object into the documentfinal Document loadedDocument = documentBuilder.newDocument();marshaller.marshal(loadedRespParty, loadedDocument);

HyperJAXB - Relational persistence for JAXB

24

Page 25: HyperJaxb Java Net

// Test that initial document and loaded document are identicalXMLAssert.assertXMLIdentical("Initial document and loaded document differ.",new Diff(document, loadedDocument), true);

}catch (Exception ex){ex.printStackTrace();Assert.fail("Unexpected exception is thrown.");

}}

For testing, we use the following sample XML file:

<metadata xmlns="http://www.fzi.de/dbs/tests/iso19115"><fileIdentifier>id000001</fileIdentifier><language>en</language><hierarchyLevel>dataset</hierarchyLevel><hierarchyLevel>series</hierarchyLevel><identificationInfo><abstract>The abstract.</abstract><purpose>The purpose.</purpose><status>planned</status><geographicBox><extentTypeCode>true</extentTypeCode><westBoundLongitude>11.7254223679</westBoundLongitude><eastBoundLongitude>11.8123425682</eastBoundLongitude><southBoundLatitude>48.3282639631</southBoundLatitude><northBoundLatitude>48.4438272635</northBoundLatitude>

</geographicBox></identificationInfo>

</metadata>

When we save object structure unmarshalled from this XML file, Hibernate executes the following SQL state-ments (identifiers are shortened for better readability):

INSERT INTO EXGEOGRAPHICBOUNDINGBOX VALUES('id-0003',true,48.4438272635,48.3282639631,11.8123425682,11.7254223679)INSERT INTO MDIDENTIFICATION VALUES('id-0002','planned','The purpose.','id-0003','The abstract.')INSERT INTO MDMETADATA VALUES('id-0001','id000001','en','id-0002')INSERT INTO METADATA VALUES('id-0001')INSERT INTO MDMETADATA_HIERARCHYLEVELINTERNAL VALUES('id-0001','dataset',0)INSERT INTO MDMETADATA_HIERARCHYLEVELINTERNAL VALUES('id-0001','series',1)

Finally we have the following content in tables of our database:

Table 1. Contents of MDMetadata table

idInternal fileIdentifier language identificationInfo

id-0001 id000001 en id-0002

Table 2. Contents of Metadata table

parentid

id-0001

Table 3. Contents of MDMetadata_HierarchyLevelInternal table

HyperJAXB - Relational persistence for JAXB

25

Page 26: HyperJaxb Java Net

MDMetadataImpl_id HierarchyLevelInternal_index value

id-0001 0 dataset

id-0001 1 series

Table 4. Contents of MDIdentification table

idInternal status purpose geographicBox abstract

id-0002 planned The purpose. id-0003 The abstract.

Table 5. Contents of EXGeographicBoundingBox table

idInternal extentTypeCode northBoundLat-

itude

southBoundLat-

itude

eastBoundLon-

gitude

westBoundLon-

gitude

id-0003 true 48.4438272635 48.3282639631 11.8123425682 11.7254223679

7. Acknowledgements

HyperJAXB and the sample application use or include the following products, libraries and tools:

• JAXB and XJC;Java Architecture for XML Binding (JAXB) provides a convenient way to bind an XML schema to a rep-resentation in Java code. You can find Jaxb at http://java.sun.com/xml/jaxb/.

• Hibernate;Hibernate is a high performance object/relational persistence and query service for Java. You can find Hi-bernate at http://www.hibernate.org

• Jakarta Commons libraries (Collections, Lang, Logging, BeanUtils, JXPath); The Jakarta Commons librar-ies are dedicated to creating and maintaining reusable Java components. The libraries are:

• Collections - provides a suite of classes which extend the Java Collections Framework.

• Lang - This library provides extra funtionality for classes in java.lang.

• Logging - This library is a wrapper around a variety of logging API implementations.

• BeanUtils - provides easy-to-use wrappers around the Java reflection and introspection APIs.

• JXPath - provides utilities for manipulating Java classes that conform to the JavaBeans naming conven-tions using the XPath syntax. The library also supports maps, DOM and other object models.

Jakarta Commons can be found at http://jakarta.apache.org

• DOM and SAX;DOM (Document Object Model) is a language neutral programming interface for XML-processing. Access-ing the XML is done via the object tree - rather than running through the document sequentially as done inSAX (Simple API for XML). DOM is well suited for smaller documents since the access is more comfort-

HyperJAXB - Relational persistence for JAXB

26

Page 27: HyperJaxb Java Net

able while SAX is better suited for bigger documents. While DOM can create XML documents SAX cannot. You can find DOM at http://www.w3.org/DOM/ and SAX at http://www.saxproject.org

• Dom4j;Dom4j is a open source library for working with XML, XPath and XSLT for the Java platform using theJava Collenctions Framework. It has full support for DOM, SAX and JAXP. You can find Dom4j at ht-tp://www.dom4j.org/

• Xalan and Xerces;Xalan and Xerces are both part of the Apache XML project ( http://xml.apache.org). Xalan is a XSLTstylesheet processor and Xerces is a XML parser. Yet, Xerces can not only parse XML files but also gener-ate them. Xalan for Java can be found at http://xml.apache.org/xalan-j/index.html, Xerces for Java at ht-tp://xml.apache.org/xerces2-j/index.html.

• CGLib;CGLib, the Java Code Generation Library, can be used to extend Java classes and implement Java inter-faces at runtime. You can find CGLib at http://cglib.sourceforge.net

• HSQL database;The Hypersonic SQL database is a Java database engine with a standard SQL and JDBC interface. Throughit's compactness, it is well suited for usage as a server in applets and applications. You can find the Hyper-sonic SQL database at http://hsql.sourceforge.net

• Programmer's Friend components;The Programmer's Friend class library contains a large collection of Java utility classes which can simplifyprogramming compared to just using the JDK. Programmer's Friend can be found at ht-tp://www.programmers-friend.org/

• JUnit;JUnit is a regression testing framework for developers who implement unit test cases. JUnit has a consoleuser interface as well as a GUI. You can find JUnit at: http://www.junit.org

• XML Unit;XML Unit extends JUnit (and NUnit) to enable unit testing of XML. It can compare a control XML docu-ment to a test document, validate documents and compare results of XPath expressions. XML Unit can befound at http://xmlunit.sourceforge.net/

8. Legal

This product includes software developed by the Apache Software Foundation (http://www.apache.org/).

This product includes Hypersonic SQL.

HyperJAXB - Relational persistence for JAXB

27