Wednesday, August 05, 2009

Rapid Java Webservice Prototyping with Hyperjaxb (Part 1)

Something different: a hands-on example of integrating a number of FOSS/COTS tools into one useful suite for rapid prototyping.

Prerequisites:
JBoss 4.2.3
Hyperjaxb 0.5.3
Oracle 9i (although the database doesn’t really matter)
JDK 1.6.

Rapid prototyping with Hyperjaxb
This assumes JBoss 4.2.3, Hyperjaxb 0.5.3, Oracle 9i (although the database doesn’t really matter) and JDK 1.6.
Foreword: Hyperjaxb provides an object-relational mapping for JAXB objects. That means that a JAXB-compliant bean can be mapped to database persistence. Leveraging JAX-B and Hibernate (hence the name), it can take a schema and generate JAXB and Hibernate annotations in the generated beans. This feature allows for very rapid Web Service development, since the beans that your service uses are the exact same objects as you are saving into the database, and are generated with little more effort than writing the schema.

Step 1. Create a schema. For our example, we will use the well-known PurchaseOrder xsd from
http://www.w3.org/TR/xmlschema-0/#POSchema. Since it’s short, we’ll just include it here:


<xsd:schema xsd="http://www.w3.org/2001/XMLSchema">

<xsd:annotation>
<xsd:documentation lang="en">
Purchase order schema for Example.com.
Copyright 2000 Example.com. All rights reserved.
</xsd:documentation>
</xsd:annotation>

<xsd:element name="purchaseOrder" type="PurchaseOrderType">

<xsd:element name="comment" type="xsd:string">

<xsd:complextype name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress">
<xsd:element name="billTo" type="USAddress">
<xsd:element ref="comment" minoccurs="0">
<xsd:element name="items" type="Items">
</xsd:element>
<xsd:attribute name="orderDate" type="xsd:date">
</xsd:attribute>

<xsd:complextype name="USAddress">
<xsd:sequence>
<xsd:element name="name" type="xsd:string">
<xsd:element name="street" type="xsd:string">
<xsd:element name="city" type="xsd:string">
<xsd:element name="state" type="xsd:string">
<xsd:element name="zip" type="xsd:decimal">
</xsd:element>
<xsd:attribute name="country" type="xsd:NMTOKEN" fixed="US">
</xsd:attribute>

<xsd:complextype name="Items">
<xsd:sequence>
<xsd:element name="item" minoccurs="0" maxoccurs="unbounded">
<xsd:complextype>
<xsd:sequence>
<xsd:element name="productName" type="xsd:string">
<xsd:element name="quantity">
<xsd:simpletype>
<xsd:restriction base="xsd:positiveInteger">
<xsd:maxexclusive value="100">
</xsd:maxexclusive>
</xsd:restriction>
</xsd:simpletype>
<xsd:element name="USPrice" type="xsd:decimal">
<xsd:element ref="comment" minoccurs="0">
<xsd:element name="shipDate" type="xsd:date" minoccurs="0">
</xsd:element>
<xsd:attribute name="partNum" type="SKU" use="required">
</xsd:attribute>
</xsd:element>
</xsd:element>
</xsd:element>

<!-- Stock Keeping Unit, a code for identifying products -->
<xsd:simpletype name="SKU">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\d{3}-[A-Z]{2}">
</xsd:pattern>
</xsd:restriction>
</xsd:schema>


This schema defines a purchaseOrder object as contaiing shipping information (shipTo), billing information (billTo), and the ordered item information (items).

Step 2. Compile the schema. The relevant portion of the build.xml looks like this:


<xjc destdir="${basedir}/target/generated-sources/xjc" extension="true">
<arg line=" -Xhyperjaxb3-ejb
-Xhyperjaxb3-ejb-persistenceUnitName=PurchaseOrder -Xhyperjaxb3-ejb-roundtripTestClassName=RoundtripTest
-Xequals
-XhashCode
-XtoString">
<binding dir="schemas">
<include name="jaxbBinding.xml">
</include>
<schema dir="schemas">
<include name="po.xsd">
</include>
<classpath>
<fileset dir="${basedir}/lib">
<include name="*.jar">
</include>
</fileset>
</classpath>
</schema>
</binding>
</arg>
</xjc>


Which tells xjc (jaxb compiler) to allow extension, use hyperjaxb-ejb as an extension, and compile po.xsd. Binding allows schema overrides to be custom-defined. This creates 4 classes: Items.java, ObjectFactory.java, PurchaseOrderType.java and USAddress.java Let’s look at PurchaseOrderType.java. You’ll notice that the JAXB annotations are present on the class variables. In addition, the JPA/Hibernate annotations are present on the accessor methods. Also, note that a field not defined in the schema, hjid, has been added. This will serve as the primary key for the table since one was not defined in the schema. Later, we’ll show you how to handle this.
For the field shipTo, the declaration looks like this:

@XmlElement(required = true)
protected generated.USAddress shipTo;

and the accessor methods look like this:
@ManyToOne(targetEntity = generated.USAddress.class, cascade = {
CascadeType.ALL
})
@JoinColumn(name = "SHIPTO_PURCHASEORDERTYPE_ID")
public generated.USAddress getShipTo() {
return shipTo;
}

And

public void setShipTo(generated.USAddress value) {
this.shipTo = value;
}

Note that shipTo declares a ManyToOne relation, which indicates that many shipTo elements will reference this one PurchaseOrder, and will contain a foreign key to the primary key in the PurchaseOrder (the hjid).

Step 3. Hibernate configuration. Obviously, this will vary depending on your environment and your intended target. In this case, we will be creating a web service, served by JBoss, and connected to an Oracle database. In any case, you will need a persistence.properties file to define connection properties. For example:


hibernate.dialect=org.hibernate.dialect.Oracle9iDialect
hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver
hibernate.connection.username= user
hibernate.connection.password=password
hibernate.hbm2ddl.auto=create-drop
hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider
hibernate.jdbc.batch_size=0
hibernate.connection.datasource=/OracleDS


This tells hibernate that it will connect to an Oracle9i database using the Oracle driver, connected via JNDI to /OracleDS. Configuration of OracleDS can be found in the JBoss documentation. For our example, we’ll assume it is set up and working. Note that Hibernate allows us to replace the underlying datastore without requiring any change to the business logic. That means the dialect parameter would be the only change required to migrate to another DBMS.

That's enough for today. The next entry will focus on writing the business logic to use the generated beans in a web service and deploying the web service into the application server.

No comments: