YAWL can be used in rapid prototyping to quickly create workflows with forms, but if you want to persist the data in the workflow, you usally have to create your own persistence layer and classes. The Hyperjaxb3-Framework allows persisting data types defined in the XML-Schema with hibernate. You only need to input a valid XML-Schema of the data types you want to persist and Hyperjaxb3 automatically generates all persistence classes and methods. This tutorial basically builds on top the Hyperjaxb3 tutorial here. In this tutorial, we take the .jar file generated by the Hyperjaxb3 tutorial containing the persistence classes and use it to persist an instance of the data type. The data type will be input by a user in the generated YAWL forms and saved by a very simple codelet. All files will be provided to test on your own YAWL environment and you can try this comfortable persistence method without having to write a line of code.

The first step was adjusting the XML-Schema data type from the Hyperjaxb3 tutorial and make it work with the YAWL automatically generated forms. Basically all "attributes" and "ref" elements have been transformed to normal string type elements:

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

  <xsd:annotation>
    <xsd:documentation xml: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 name="comment" type="xsd:string" minOccurs="0"/>
      <xsd:element name="items" type="Items"/>
      <xsd:element name="orderDate" type="xsd:date"/>
    </xsd:sequence>
  </xsd:complexType>

  <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 name="country" type="xsd:NMTOKEN" fixed="US"/>
    </xsd:sequence>
  </xsd:complexType>

  <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:restriction>
              </xsd:simpleType>
            </xsd:element>
            <xsd:element name="USPrice" type="xsd:decimal"/>
            <xsd:element name="comment" type="xsd:string" minOccurs="0"/>
            <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
            <xsd:element name="partNum" type="SKU" />
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:sequence>
  </xsd:complexType>

  <!-- 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:restriction>
  </xsd:simpleType>

</xsd:schema>

If you completed the Hyperjaxb3 tutorial, you should have a class to test persisting and loading xml-formatted data types. The following class has been created by me and has the method start(..), that can persist a string of the XML-Schema type "purchaseOrderType" defined above and returns the id in the database:

public class Helper {
    
private JAXBContext context;

private ObjectFactory objectFactory;

private EntityManagerFactory entityManagerFactory;
    
public long start(Object o) throws Exception{
    
    setUp();
        
    final Object object;
    final Unmarshaller unmarshaller = context.createUnmarshaller();
    final PurchaseOrderType purchaseOrder;
       
    if(o!=null){
        String purchaseOrderString = (String) o;
        InputStream stream = new ByteArrayInputStream(purchaseOrderString.getBytes("UTF-8"));
        object = unmarshaller.unmarshal(stream);
        purchaseOrder = ((JAXBElement<PurchaseOrderType>) object).getValue();
    }else{
    
    object = unmarshaller.unmarshal(new File("src/test/samples/po.xml"));
    purchaseOrder = ((JAXBElement<PurchaseOrderType>) object).getValue();
    
    }
    
    long id = persist(purchaseOrder);
    
    return id;
    }


public long persist(PurchaseOrderType purchaseOrder){
    try {
        setPersistenceProperties();
    } catch (IOException ex) {
        Logger.getLogger(Helper.class.getName()).log(Level.SEVERE, null, ex);
    }
    
    final EntityManager saveManager = entityManagerFactory
    .createEntityManager();
    saveManager.getTransaction().begin();
    saveManager.persist(purchaseOrder);
    saveManager.getTransaction().commit();
    saveManager.close();
    
    return purchaseOrder.getHjid();
}

public String loadAsString(long id){
    final EntityManager loadManager = entityManagerFactory
        .createEntityManager();
    final PurchaseOrderType beta = loadManager.find(
                    PurchaseOrderType.class, id);
    
    final Marshaller marshaller;
    OutputStream os = new ByteArrayOutputStream();
    
    try {
        marshaller = context.createMarshaller();
        marshaller.marshal(objectFactory.createPurchaseOrder(beta), os);
    } catch (JAXBException ex) {
        Logger.getLogger(Helper.class.getName()).log(Level.SEVERE, null, ex);
    }
    
    loadManager.close();
            
    return os.toString();
}


public void setPersistenceProperties() throws IOException{
    
    final Properties persistenceProperties = new Properties();
    InputStream is = null;
    try {
            is = getClass().getClassLoader().getResourceAsStream(
                            "persistence.properties");
            persistenceProperties.load(is);
    } finally {
            if (is != null) {
                    try {
                            is.close();
                    } catch (IOException ignored) {
                    }
            }
    }

    entityManagerFactory = new Persistence().createEntityManagerFactory("generated", persistenceProperties);
    }  

protected void setUp() throws Exception {
    context = JAXBContext.newInstance("generated");
    objectFactory = new ObjectFactory();
    }
    
public void setUpPersistence() throws Exception {
    entityManagerFactory = Persistence.createEntityManagerFactory("generated");
    }

}

The start(..) method transforms the string into a class, that has been generated by the Hyperjaxb3 framework, and represents it. After initialization, the object is given to the entitiyManager, which saves it in the database. This class loads the information for the database connection from the file "persistence.properties". If you want to try it out on your system, you will have to change the parameters in the file "persistence.properties" in the "hyperjaxb3-ejb-template-basic-maven-0.5.6.jar". In this tutorial, the database used is postgres on the default port 5432 and the name of the database is "hyperjax".

In NetBeans the tutorial project needs to be built and the resulting jar file contains the above helper class, persitence properties and all java representations of the XML-Schema data types. Now a codelet has to call the method in the helper class. We have a tutorial on how to create a codelet here. With the above 'helper' class, the codelet has to get the purchaseOrderType XML-Element, transform it to an XML-String and call the start method:

public class HyperJAXBCodelet extends AbstractCodelet {

    public HyperJAXBCodelet(){
        super();
        setDescription("This is a HYperJAXB test codelet");
    }

    @Override
    public Element execute(Element inData, List<YParameter> inParams,
            List<YParameter> outParams) throws CodeletExecutionException {
        
        super.setInputs(inData, inParams, outParams);
            
        Element order = inData.getChild("purchaseOrder");
        
        Helper h = new Helper();
        
        XMLOutputter xml = new XMLOutputter();        
        
        long id = 0;
        
        try {
            id = h.start(xml.outputString(order));
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        setParameterValue("hjid", ""+id);
        
        try {
            
            getOutputData().addContent(new Element("purchaseOrder"));
            
            getOutputData().getChild("purchaseOrder")
                    .addContent(stringToElement(h.loadAsString(id)).cloneContent());
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return getOutputData();
    }

    private Element stringToElement(String string) throws JDOMException, IOException {

        SAXBuilder builder = new SAXBuilder();
        Reader in = new StringReader(string);
        Document doc = null;
        Element root = null;
        
        doc = builder.build(in);
        root = doc.getRootElement();
        
        return root;
    }

}

 

After saving the element via the start(..) method, the element is loaded from the database as an XML-String with the loadAsString(id) method of the 'helper' class using the hyperjax id. The XML-String is converted back to the "Element" type YAWL is using and passed on to use in the workflow. All files, that are needed to this tutorial are attached. If you want to use the Hyperjaxb3 framework with YAWL, you will need to add a few libraries, that are in the libs folder of the attached archive. Add these libraries to the tomcat libs folder. To change the persistence connection, you need to change the persistence.properties file in the "hyperjaxb3-ejb-template-basic-maven-0.5.6.jar" file. A workflow to test the codelet has been included as well. Copy the codelet class file "HyperJAXBCodelet.class" to the codelet folder of the resource service: "\YAWL4Study-2.3final\engine\apache-tomcat-6.0.18\webapps\resourceService\WEB-INF\classes\org\yawlfoundation\yawl\resourcing\codelets" to be able to execute the workflow properly.

In conclusion, it is possible to persist complex XML-Schema data types using the hyperjaxb3 framework. The persistence is not dynamic, because the java objects of the have to be build first, but once that is done, entries can easily be persisted and loaded from the database using the hyperjax id.

 

In the attachment you will find:

  1. NetBeans project with the helper class and adjusted XML-Schema
  2. The workflow you can use to try out persistence with the hyperjaxb3 framework
  3. The codelet, that is execeuted in the workflow to save and load instances of the adjusted purchaseOrder type
  4. The libraries you need to run the hyperjaxb3 framework and this tutorial on YAWL 2.3

(Notice: This tutorial is based on  YAWL version 2.3.x)

Christoph Lehnert

Thu, 10/24/2013 - 20:44

There might be problems with the HyperJAXBCodelet if you are using YAWL4Study 2.3.5. In this case try to compile it with org.jdom2.Element and org.jdom2.Element instead of using org.jdom.Element and org.jdom.Element.