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.

No comments: