Wednesday, January 25, 2012

Pattern: Building Reusable Java-Based BPM Task Form- Part 1

Overview

This series provides a simple and flexible alternative way of creating task forms for human task activities in Oracle SOA/BPM Suite 11g. The pattern utilizes the power of Java and ADF Bounded Task Flows to simplify manipulation of task payloads and promote task form reusability, flexibility, and simplicity. Part 1 discusses about the problems and the corresponding solutions to attain the objective of having a reusable task form that can simplify the manipulation of task payload. The nitty-gritty details of the implementation of the generic task form will be discussed in Part 2, and the convenience and capabilities of this pattern will be demonstrated in Part 3.

Problem Description

JDeveloper provides a mechanism to easily generate task forms for human task activities but the generated artifacts are mostly XML based and undeniably complex. Manipulation of payloads are very challenging especially if your domain models follows an Object Oriented approach, wherein you need not only capture object IDs but also all the other relevant attributes. Incorporation of complex ui components and validation scheme is a nightmare. Task form reuse and maintainability is compromised due to the need to generate different task forms for every change in task payload elements.



Figure 1: JDeveloper Generated XML Artifacts


Technical Pattern Description

This document is about creating a BPM task form based on Java with the following features:
  • a dynamic region to handle different task payload elements;
  • actions are dynamic with regards to the access rights and the applicable outcomes/operations of a specific task;
  • task payloads can be transformed into POJOs for convenient presentation, manipulation, and validation; and
  • can be launched from the standard bpm worklist application.


Figure 2: A Java-Based BPM Task Form
Before proceeding further, I need to establish first the feasibility and the benefits of such a reusable task form based on Java (as opposed to XML).

A task form for BPM human task is generally compose of the following sections:
  1. Task Actions
  2. Task Details
  3. Task Contents (Payload)
  4. Task History
  5. Comments
  6. Attachments
All these sections are generic except for the Task Contents(Payload), so why not implement the Task Contents section as a dynamic region? In such case, we remove the necessity to generate different task forms for every change in task payload elements. This should be easy if we could only use the Jdeveloper generated task form and readily get the oracle.bpel.services.workflow.task.model.Task object, but such is not the case, because the object that is retrieved by the "getTaskDetails" method action is a sort of XML Object that only ADF understands, plus the fact that the generated artifacts are only applicable to a specific human task because of the explicitly generated access rights and outcomes action bindings in the generated page definition file. If you look at the page definition file in figure 1, you can see the following action bindings: Claim; Acknowledge; Resume; Withdraw; APPROVE; and REJECT. Section 29.11 (Reusing the Task Flow Application with Multiple Human Tasks) of the developers guide states:
You can reuse a single task flow application with multiple human tasks. To use this feature, all human tasks must have identical payload elements.
I doubt this information because what if the assigness have differently configured access rights and applicable Outcomes? Anyone from Oracle?

In short, we need to create our own simple implementation of reusable task form based on Java using the Human Task Services API that retrieves an oracle.bpel.services.workflow.task.model.Task object.

The next problems are as follows:
  1. How do we pass the taskflowId that our dynamic region requires, since that is not provided in the remote taskflow-call made by the standardard worklist application?

  2. How do we retrieve the updated payload after it has been displayed in the dynamic region and manipulated by the user?

The solution to problem#1 is to create wrapper taskflows wherein we can provide the appropriate taskflowId. This wrapper taskflow also contains all the required task flow input parameter that is needed by the generic human task that contains the task form with a dynamic region. In this wrapper tasflow, we also inject the necessary payload handler that is required to answer problem#2.

The solution to problem#2 is the pass-by-reference mechanism of task-input-parameters. We can pass a handler that contains the initial payload to the dynamic region and later access the same handler to get the updated payload. The handler will be responsible for marshalling and unmarshalling of xml contents into Java domain models and vice-versa. The handler will also contain the map of privileges that the user has with regards to the payload. Since the marshalling and unmarshalling process varies for every type of payload elements then we need to make the handler as an abstract class with two abstract methods- marshal and unmarshal.



Figure 3: A Simple Wrapper Task Flow Containing the Generic Task Flow
The AbstractPayloadHandler class.
package soadev.bpm.view.helper;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import oracle.bpel.services.workflow.metadata.IPrivilege;
import oracle.bpel.services.workflow.task.model.AnyType;

import org.w3c.dom.Element;

public abstract class AbstractPayloadHandler {
    protected AnyType payload;
    protected Map<String, IPrivilege> visibilityRules;
    protected Map<String, Object> payloadObjects;

    public Map<String, Object> getPayloadObjects() throws Exception {
        if(payload == null){
            throw new IllegalStateException("Payload not set.");
        }
        if (payloadObjects == null) {
            List<Element> elements = payload.getContent();
            payloadObjects = new HashMap<String, Object>();
            for (Element element : elements) {
                Object obj = unmarshal(element);
                payloadObjects.put(element.getNodeName(), obj);
            }
        }
        return payloadObjects;
    }
    
    public Map<String, Element> getPayloadElements() throws Exception {
        Map<String, Element> payloadElements = new HashMap<String, Element>();
        for (Map.Entry<String, Object> entry :
             getPayloadObjects().entrySet()) {
            if(entry.getKey() != null && entry.getValue() != null){
                Element element = marshal(entry.getValue(), entry.getKey());
                payloadElements.put(entry.getKey() , element);
            }
        }
        return payloadElements;
    }
        
    public abstract Object unmarshal(Element element)throws Exception;
    
    public abstract Element marshal(Object object, String elementName)throws Exception;

    public void setPayload(AnyType payload) {
        this.payload = payload;
    }

    public AnyType getPayload(){
        return payload;
    }

    public void setVisibilityRules(Map<String, IPrivilege> privileges) {
        this.visibilityRules = privileges;
    }

    public Map<String, IPrivilege> getVisibilityRules() {
        if(visibilityRules == null){
            throw new IllegalStateException("Visibility Rules not set.");
        }
        return visibilityRules;
    }
}
The payload and visibilityRules attributes needs to be set upon the initialization of the task form.
public class TaskDetailsForm {
    ...
    public void initPayload() throws Exception {
        AbstractPayloadHandler handler =
            (AbstractPayloadHandler)ADFUtils.getPageFlowScope().get("payloadHandler");
        if (handler != null) {
            handler.setPayload(getTask().getPayload());
            handler.setVisibilityRules(this.getTaskVisibilityRules());
        }
    }
    ...
And the getPayloadElement will be called inside an updatePayload() method in the TaskDetailsForm. The updatePayload() will be invoked just before any task action like Submit or Approve will be invoked.
public void updatePayload() throws Exception {
        AbstractPayloadHandler container =
            (AbstractPayloadHandler)ADFUtils.getPageFlowScope().get("payloadContainer");
        if (container != null) {
            Map<String, Element> payloadElements = container.getPayloadElements();
            Element payloadElement = getTask().getPayloadAsElement();
            for (Map.Entry<String, Element> entry :payloadElements.entrySet()) {
                if(hasWriteAccess(entry.getKey())){
                    PayloadUtil.replaceOrAppendPayloadElementData(payloadElement, entry.getValue());
                }
            }
            getTask().setPayloadAsElement(payloadElement);
        }
    }
Below is a sample class that extends the AbstractPayloadHandler class. This class takes advantage of JaxbContext for the marshalling and unmarshalling of XML elements to domain objects.
package soadev.bpm.view.helper;

import com.oracle.xmlns.bpm.bpmobject.hellotypes.helloobject.HelloObjectType;
import com.oracle.xmlns.bpm.bpmobject.hellotypes.reviewobject.ReviewObjectType;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class HelloWorldPayloadHandler extends AbstractPayloadHandler {
    private JAXBContext jaxbContext;

    public HelloWorldPayloadHandler() {
        try {
            jaxbContext =
                    JAXBContext.newInstance(HelloObjectType.class, ReviewObjectType.class);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    public Object unmarshal(Element element) throws Exception {
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Object obj = unmarshaller.unmarshal(element);
        return obj;
    }

    public Element marshal(Object object,
                                          String elementName) throws Exception {
        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        DocumentBuilder docBuilder =
            DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = docBuilder.newDocument();
        marshaller.marshal(object, document);
        Element element = document.getDocumentElement();
        return element;
    }
}
The HelloObjectType and ReviewObjectType classes were created by copying the HelloObject.xsd and ReviewObject.xsd from the sample HelloWorld_OBE BPM application into the task form project and invoking the Generate Jaxb 2.0 Content Model wizard of JDeveloper.


Figure 4: Generate JAXB 2.0 Content Model Wizard
To learn more about marshalling and unmarshalling Pojos, SDOs, and XML Elements, Please see my previous posts as follows:

Summary

In this post, I have demonstrated the feasibility of having a reusable task form with the help of wrapper task flows and an AbstractPayloadHandler class that facilitates the passing and retrieving of updated payloads to a dynamic region.

5 comments:

  1. Hi Rommel,

    I dont have much idea of BPM but i am good in ADF,

    so just want to know the best practicews in terms of my requirment,

    we have 8-10 letters, now each letter is intiated and user will generate the letter, now that letter will point to human task form created in ADF just to show user and edit if anything he wants before sending that letter.

    now i am facing a challenge, for all the letter objects if we kept the same payload. I created different .jsff pages(regions) for each letter and dynamically opening a particular letter (based on the parameter coming from BPM) in taskdetails1.jspx. now how can i return the taskflow call to BPM from the dynamic region.

    also if you can suggest on BPM side if we should generate different payload for each letter. Or any better approach to do the same.

    ReplyDelete
  2. Hi Rommel,

    excuse me, in the updatePayload() where i can find the PayloadUtil Class?

    regards

    Erick

    ReplyDelete
  3. Hi eYiCOCKo,
    Please see the sample file on my second post: http://soadev.blogspot.com/2012/01/java-based-bpm-task-form-2.html

    regards,
    ROmmel

    ReplyDelete
  4. Hi Rommel,
    i understood maximum of the code ,
    please send me steps how to add Oracle BPM libray into ADF project and how to deploy that.

    i am getting error in deploying the application
    like:Failed to register library Extension-Name: wlfullclient, Implementation-Version: 10.3.5.0: Library cannot have Implementation-Version set, without also specifying its Specification-Version.


    Regards,
    Shailendra

    ReplyDelete
  5. Hi Rommel,

    How we can implement the Dynamic Assignment..
    My req. is like i have a supervisor where he receives 30 request . He need to reassign the task to his 3 Team leads 10 request for each Team lead. And this has to be done dynamically if his skills matches.


    Regards,
    Pavan

    ReplyDelete