Monday, August 10, 2009

Rapid Java Webservice Prototyping with Hyperjaxb (Part 2)

We resume the HyperJAXB example with some business logic.

Step 4: Let’s start with the basics: save, delete and query. We’ll leverage Hiberate’s ability to perform a saveOrUpdate, so we don’t need separate methods for both. Create a class, PurchaseOrderPersistence, and initialize Hibernate. I chose to use a singleton, just to make sure that the access will be atomic. So I’ve got two methods:

public static PurchaseOrderPersistence getInstance() {
if (instance == null) {
instance = new PurchaseOrderPersistence();
}
return instance;
}

private PurchaseOrderPersistence() {
persistenceProperties = new Properties();
InputStream is = null;
try {
System.out.println("loading properties");
is = PurchaseOrderPersistence.class.getClassLoader()
.getResourceAsStream("persistence.properties");
System.out.println(PurchaseOrderPersistence.class.getClassLoader().getResource("persistence.properties"));
System.out.println("is: " + is.toString());
persistenceProperties.load(is);
System.out.println("props: " + persistenceProperties.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ignored) {
}
}
}
entityManagerFactory = Persistence.createEntityManagerFactory(
" PurchaseOrder ", persistenceProperties);
}

What it’s doing is looking for the persistence.properties file and passing it to the EntityManagerFactory along with the persistence unit name (PurchaseOrder) defined in the build. Remember that we created persistence.properties in step 3 to define our Hibernate connection properties. In the example, we used JNDI to bind to the datastore, but you could use a JDBC URL as well. Now that the system knows what to do with the beans, we can start creating methods. Basic getOrder method:

public PurchaseOrderType getOrder(long id) {
PurchaseOrderType order = null;
EntityManager em = null;
try {
em = entityManagerFactory.createEntityManager();
order = em.find(PurchaseOrderType.class, id);
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (em != null && em.isOpen()) {
try {
//em.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
return order;
}

Using EntityManager, we are able to lookup the requested object by ID without needing any SQL. Repeat as necessary for update and delete methods.

Step 5: Create the web service interface. Create a class to contain the web methods and use the WS annotations to declare the class as a web service. It should look like this:

@WebService(serviceName = "PurchaseOrderWS")
public class PurchaseOrderWS {

private static ObjectFactory of = new ObjectFactory();

@WebMethod
public PurchaseOrderType getOrder(long orderID) {
PurchaseOrderType task = null;
try {
PurchaseOrderPersistence pop = PurchaseOrderPersistence
.getInstance();
task = pop.getOrder(orderID);
if (task != null) {
System.out.println("getTask: " + task.toString());
}
} catch (Throwable t) {
t.printStackTrace();
}
return task;
}


This creates a web service named PurchaseOrderWS, and web method getOrder that returns a PurchaseOrderType. Keep in mind this is all without doing anything to the data object beyond defining them in the XSD. It's not 100% necessary to have the web methods in a separate class from the persistence methods, since there's a 1 to 1 mapping between them, but it's good practice to allow some flexibility in the design.

Step 6: Package and deploy the web service. Create a web.xml deployment descriptor (is this necessary with annotations?)

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>
PurchaseOrderWS</display-name>
<servlet>
<servlet-name>PurchaseOrderWS</servlet-name>
<servlet-class>com.jon.purchaseorder.PurchaseOrderWS</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PurchaseOrderWS</servlet-name>
<url-pattern>/PurchaseOrderWS</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>

This defines the mapping of the web service to the implementation class. Package a war file using ant like this:

<!-- copy ws-related stuff -->
<copy todir="${basedir}/target/classes">
<fileset dir="${basedir}/lib">
<include name="runtime-0.4.1.5.jar" />
<include name="commons-lang-2.1.jar" />
<include name="hyperjaxb*.jar" />
</fileset>
</copy>
<!-- create war file-->
<jar destfile="${basedir}/target/classes/generated-classes.jar">
<fileset dir="${basedir}/target/classes">
<include name="**/*.class" />
</fileset>
<fileset dir="${basedir}/resources">
<include name="*" />
</fileset>
</jar>
<war destfile="${basedir}/target/PurchaseOrderWS.war"
webxml="${basedir}/resources/web.xml">
<lib dir="${basedir}/target/classes">
<include name="*.jar" />
</lib>
<metainf dir="${basedir}/target/generated-sources/xjc/META-INF">
<include name="*" />
</metainf>
</war>

This will package the war file using the web.xml from above, including the META-INF directory generated by Hibernate containing the persistence.xml with the object-relational mapping file. It also includes a jar file of the generated classes, since they are compiled into a separate directory from the implementation classes. It also includes the necessary hibernate/hyperjax jars in WEB-INF/lib. Copy the resulting .war file into your JBoss/server/deploy directory and start it up. If all has gone well, you should be able to navigate to the WSDL for your web service. If you invoked the service, you would persist your data in the remote database without writing any SQL.

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.