Stardust/Knowledge Base/Integration/Data/Structured Data to Java Conversion with JAXB

From Eclipsepedia

Jump to: navigation, search

Contents

Synopsis

This article describes how to use JAXB to marshal/unmarshal between XML data and Java objects and leverage this technology in combination with Structured Data Types in Stardust.

Examples implemented with Stardust 7.0.

Problem Description

Using Structured Data Types in the Stardust process model is the most common approach for representing complex data objects. Very often XML Schemas already exist and are imported into Stardust to create the Structured Data Definitions. However, when passing a Structured Data object into a Java class during the process flow, Stardust by default converts it into a java.util.Map object which is cumbersome to deal with, because the developer has to know the keys for the value fields. It would be preferrable if the Java developer could deal with real POJOs inside the Java code while still representing these objects as Structured Data in the Stardust model.

Solution

Using JAXB you can link XML Schema definitions to POJOs and convert from/to Structured Data (almost) automatically when invoking Java Applications or Spring Beans. Personally I find it easier to start off with an XML Schema and generate the Java classes than to do it the other way around, because the generated XML Schema is often not optimal and needs to be adapted. So in this article the XML Schema definition will contain the leading data model from which everything else is generated. The examples are based on using a Utility Project with a Maven configuration.

  1. Create an XML Schema (XSD)
  2. Generate Java classes and bindings using JAXB
  3. Import the XSD into Stardust to create Structured Data Definitions
  4. Leverage some JAXB boilerplate code for marshalling/unmarshalling
  5. Tie everything together via the IN/OUT data mappings in the Stardust model

Create an XML Schema

This is a very simple XSD, but it's enough to showcase the functionality. It defines a complex type called "Customer".

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
	targetNamespace="http://www.example.org/Customer"
	xmlns:tns="http://www.example.org/Customer"
	elementFormDefault="qualified">
 
    <complexType name="CustomerType">
    	<sequence>
            <element name="id" type="long"></element>
            <element name="firstname" type="string"></element>
    	    <element name="lastname" type="string"></element>
    	</sequence>
    </complexType>
</schema>

Store this XSD in your utility project as /src/java/resources/xsd/Customer.xsd

Generate Java Classes

To generate the Java classes from the schema we're using the standard JAXB Maven Plug-In.

Include the plugin into Maven's "generate" goal during the build:

<build>
	<plugins>
		<plugin>
			<groupId>com.sun.tools.xjc.maven2</groupId>
			<artifactId>maven-jaxb-plugin</artifactId>
			<executions>
				<execution>
					<goals>
						<goal>generate</goal>
					</goals>
				</execution>
			</executions>
			<configuration>
				<generatePackage>org.eclipse.stardust.data.jaxb</generatePackage>
				<includeSchemas>
					<includeSchema>**/xsd/*.xsd</includeSchema>
				</includeSchemas>
				<strict>true</strict>
				<verbose>true</verbose>
			</configuration>
		</plugin>
	</plugins>
</build>

Running "mvn generate-sources" will create the corresponding Java classes under target/generated-sources/xjc/ in your project.

NOTE: There's a known problem with mashalling/unmarshalling JAXB classes when the complex type is not used or delared as the root element in the XSD. To prevent these problems you can manually add the @XmlRootElement annotation to the generated Java classes. Unfortunately, I haven't found a way to generate this automatically.

Open the generated CustomerType.java class and add the following @XmlRootElement annotation to the class declaration:

@XmlRootElement(name="CustomerType")
public class CustomerType {
...

Create Structured Data Type Definitions

Create Structured Data Type definitions in Stardust by importing complex types from the XSD.

  • In the Outline View of the Stardust process model right-klick on Structured Data Types and choose Import Types
  • If you're process model resides in the same project, select Workbench Projects > Next ... then navigate to resources/xsd/Customer.xsd
  • Select the CustomerType as depicted in the screenshot below
  • Hint: Do NOT select "Save original schema in the model". This will allow you to make changes to the XSD later and they will be reflected in the model after closing and re-opening it.

Stardust-Import-structured-data-from-xsd.png

JAXB marshalling/unmarshalling

To marshal/unmarshal your code you need some boilerplate JAXB routines.
Before we look at this code, you'll have to know a few things about how Stardust currently handles passing the Structured Data into Java code and what that means for the JAXB object creation:

  • By default a Structured Type data object is passed to Java as a java.util.Map<String,Object>.
  • Using the special Data Path "DOM()" in Stardust (see the next section for a screenshot) it is possible to convert the Structured Data object into a org.w3c.dom.Element representation (after all it's a piece of XML).
  • That means that you can base your JAXB conversion on the org.w3c.dom.Element class and don't have to deal with parsing an XML string or a stream.

With that being said, you need two utility methods for unmarshalling and marshalling respectively that could look like this:

    /**
     * Unmarshalls the specified Element into a bean of the specified returnType.
     *
     * @param returnType class of the expected bean
     * @param element XML to umarshall
     *
     * @return <T> instance of the bean
     * @throws JAXBException if there's a problem during unmarshalling
     */
    public static <T> T unmarshall(Class<T> returnType, Element element) throws JAXBException
    {
    	 if (returnType == null) {
             throw new IllegalArgumentException("Expected Returntype null");
         }
         if (element == null) {
             throw new IllegalArgumentException("Passed Element null");
         }
         JAXBContext context = JAXBContext.newInstance(returnType.getPackage().getName());
         Unmarshaller unmarshaller = context.createUnmarshaller();
         Object value = unmarshaller.unmarshal(element);
 
         if(value != null) {
             if(!returnType.isInstance(value))
                 throw new IllegalArgumentException("Returntype does not match the passed Element");
         }
 
         return returnType.cast(value);
    }
 
 
    /**
     * Marshalls the specified Object into its XML representation
     *
     * @param o the object to marshall
     * @return Element the XML as an Element
     * @throws JAXBException if there's a problem during marshalling
     */
    public static Element marshallElement(Object o) throws JAXBException
    {
        try {
            if (o == null) {
                throw new IllegalArgumentException("Passed object is null");
            }
 
            JAXBContext context = JAXBContext.newInstance(o.getClass().getPackage().getName());
            Marshaller mashaller = context.createMarshaller();
 
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
 
            DocumentBuilder db = dbf.newDocumentBuilder();           
            Document doc = db.newDocument();           
            mashaller.marshal(o, doc);
 
            return (Element) doc.getFirstChild();
        }
        catch (ParserConfigurationException e) {
            LOG.error(e.getMessage(), e);
            throw new JAXBException(e.getMessage(), e);
        }
    }

The following Java class shows a simple code example that can be invoked from Stardust. Notice how the access methods deal with the org.w3c.dom.Element objects and the actual business logic in the processCustomer() method works with the real CustomerType object.

package org.eclipse.stardust.apps;
 
import javax.xml.bind.JAXBException;
 
import org.eclipse.stardust.data.jaxb.CustomerType;
import org.eclipse.stardust.data.jaxb.JaxbUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
 
public class MyCustomerProcessor {
 
	private static Logger LOG = LoggerFactory.getLogger(MyCustomerProcessor.class);
 
	private CustomerType customer;
 
	public void setCustomer( Element e ) {
		try {
			this.customer = JaxbUtils.unmarshall(CustomerType.class, e);
			LOG.debug("Successfully unmarshalled customer "+this.customer.getId());
		}
		catch (JAXBException e) {
			LOG.error("Unable to unmarshal customer object.", e);
		}
	}
 
	public Element getCustomer() {
		try {
			Element element = JaxbUtils.marshallElement(this.customer);
			LOG.debug("Successfully marshalled customer "+this.customer.getId());
			return element;
		} catch (JAXBException e) {
			LOG.error("Unable to marshal customer object.", e);
		}
		return null;
	}
 
	public void processCustomer() {
		LOG.debug("Processing customer ID: "+this.customer.getId());
		// ... do something with the customer object
	}
}

Invoking the application from Stardust

The following screenshots show how the described artifacts can be tied together and used in a BPM process which uses a Structured Data object inside the Stardust model and still allows to convert it to a CustomerType Java object for processing.

  1. Create a Plain Java Application in the Stardust model
    (a Spring Bean Application would work just as easily, but beware, because the above described MyCustomerProcessor.java is a stateful bean and needs to be defined with scope="prototype" in your Spring context!)

    Stardust-POJO-Application-MyCustomerProcessor.png

  2. Use the application together with the Customer Structured Data object to create a process flow

    Stardust-ProcessDefinition-ProcessCustomer-JAXB.png

  3. Configure the IN- and OUT-data-mappings on the "Process Customer" activity in the following way to map the Customer object to/from the Java application

    Stardust-POJO-Application-MyCustomerProcessor-IN-mapping.png

    Stardust-POJO-Application-MyCustomerProcessor-OUT-mapping.png