Saturday, January 28, 2012

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

Overview

This is Part 2 of the series that provides a simple and flexible alternative way of creating task forms for human task activities in Oracle SOA/BPM Suite 11g. This part will discuss the nitty-gritty details of the implementation of the generic task form based on Java using the Human Workflow Services API. The problems and the corresponding solutions to attain the objective of having a reusable task form that can simplify the manipulation of task payload was discussed in Part 1, and the convenience and capabilities of this pattern will be demonstrated in Part 3.


The Human Workflow Services API

Any custom worklist or task form application accesses the various workflow services through the workflow service client.
The WorkflowServiceClientFactory
The window to the Human Workflow Services is through the WorkflowServiceClientFactory. To have a handle to the workflow service client, it is required to have a workflow client configuration. The configuration can be set through a wf_client_config.xml file, a property map, or a JAXB Object.
Sample wf_client_config.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workflowServicesClientConfiguration xmlns="http://xmlns.oracle.com/bpel/services/client"
                                     clientType="REMOTE">
   <server default="true" name="default">
      <localClient>
         <participateInClientTransaction>false</participateInClientTransaction>
      </localClient>
      <remoteClient>
         <serverURL>t3://localhost:8001</serverURL>
         <initialContextFactory>weblogic.jndi.WLInitialContextFactory</initialContextFactory>
         <participateInClientTransaction>false</participateInClientTransaction>
      </remoteClient>
      <soapClient>
         <rootEndPointURL>http://localhost:8001</rootEndPointURL>
         <identityPropagation mode="dynamic" type="saml">
            <policy-references>
               <policy-reference enabled="true" category="security"
                                 uri="oracle/wss10_saml_token_client_policy"/>
            </policy-references>
         </identityPropagation>
      </soapClient>
   </server>
</workflowServicesClientConfiguration>

With a properly configured wf_client_config.xml, we can acquire a workflow client instance with minimal code:
public class TaskDetailsForm {
public class TaskDetailsForm {
    private IWorkflowServiceClient client;
...
    public IWorkflowServiceClient getWorkflowServiceClient(){
        if (client == null){
            client = WorkflowServiceClientFactory.getWorkflowServiceClient(WorkflowServiceClientFactory.REMOTE_CLIENT);
        }
        return client;
    }
...
The WorkflowContext
Most of the human workflow API requires a workflow context. Oracle recommends that you get the workflow context one time and use it everywhere, because there is a performance implication for getting the workflow context for every request. For simplicity, I store the context as an attribute of our view scoped managed bean as follows:

...
    private IWorkflowContext ctx;
...
    public IWorkflowContext getWorkflowContext() throws WorkflowException {
        if (ctx == null) {
            String ctxToken =
                (String)ADFUtils.getPageFlowScope().get("bpmWorklistContext");
            ctx = getWorkflowServiceClient().getTaskQueryService().getWorkflowContext(ctxToken);
        }
        return ctx;
    }



Getting the Task object

The task object contains all the information required to be displayed on a BPM task form like task details, process info, comments and attachments. In addition, the task object also contains the corresponding applicable system and custom actions.
The task object can be retrieve through the ITaskQueryService as follows:
...
   private Task task;
...
    public Task getTask() throws WorkflowException {
        if (task == null) {
            task = getTaskVersionDetails();
        }
        return task;
    }
    
    public Task getTaskVersionDetails() throws WorkflowException {
        ITaskQueryService queryService =
            getWorkflowServiceClient().getTaskQueryService();
        IWorkflowContext ctx = getWorkflowContext();
        String taskId =
            (String)ADFUtils.getPageFlowScope().get("bpmWorklistTaskId");
        String taskVersion = (String)ADFUtils.getPageFlowScope().get("bpmWorklistTaskVersion");
        if (taskVersion == null){
            return queryService.getTaskDetailsById(ctx, taskId);
        }else{
            return queryService.getTaskVersionDetails(ctx, taskId, getSafeInt(taskVersion));
        }
    }

Displaying Task Details

In creating the task details page, we will follow the same layout as the one generated by JDeveloper Create Task Form wizard.
The following are steps to be done.

  1. Update faces-config.xml to reuse existing message bundle.
  2. Create a bounded task flow.
  3. Register a TaskDetailsForm managed bean in viewScope.
  4. Create the taskDetails.jspx
Update faces-config.xml to reuse existing message bundle.
I use the existing message bundle in internationalizing labels by updating our faces-config.xml to as follows:
<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
  <application>
    <default-render-kit-id>oracle.adf.rich</default-render-kit-id>
    <locale-config>
      <default-locale>en</default-locale>
    </locale-config>
    <message-bundle>oracle.bpel.services.workflow.worklist.resources.worklist</message-bundle>
    <resource-bundle>
      <base-name>oracle.bpel.services.workflow.worklist.resources.worklist</base-name>
      <var>resources</var>
    </resource-bundle>
  </application>
</faces-config>
Create a bounded task flow
I create a bounded task flow based on jspx pages as opposed to page fragments. I then copy all the task flow input parameters defined on a standard JDeveloper generated task form. In addition, I also add the following task flow input parameters:
...
    <input-parameter-definition id="__171">
      <name id="__172">payloadHandler</name>
      <value>#{pageFlowScope.payloadHandler}</value>
      <class>soadev.bpm.taskforms.view.helper.AbstractPayloadContainer</class>
    </input-parameter-definition>
    <input-parameter-definition id="__174">
      <name id="__173">payloadTaskFlowId</name>
      <value>#{pageFlowScope.payloadTaskFlowId}</value>
    </input-parameter-definition>
...
The payloadTaskflowId is the task flow id of the region that will be displayed on the Contents section of the task form. The class AbstractPayloadContainer has been described in Part 1 of this series. This class will facilitate the passing and acquiring of the updated payload to/ from the dynamic region.
Register a TaskDetailsForm managed bean in viewScope
<managed-bean id="__4">
  <managed-bean-name id="__1">taskDetailsForm</managed-bean-name>
  <managed-bean-class id="__2">soadev.bpm.view.backing.TaskDetailsForm</managed-bean-class>
  <managed-bean-scope id="__3">view</managed-bean-scope>
</managed-bean>
Create the taskDetails.jspx
Task information can now be displayed by accessing the task object in the TaskDetailsForm like as follows:
<af:panelLabelAndMessage id="creator"
                      label="#{resources.CREATOR}">
    <af:outputText value="#{viewScope.taskDetailsForm.task.creatorDisplayName}"
                      id="ot7"/>
  </af:panelLabelAndMessage>
  <af:panelLabelAndMessage id="createdDate"
                      label="#{resources.CREATE_DATE}">
    <af:outputText value="#{viewScope.taskDetailsForm.task.systemAttributes.createdDate}"
                      noWrap="true" id="ot9">

Dynamic Task Actions

Task Actions are composed of system actions and custom actions. System actions are standard human interactions like, Skip, Delegate, Suspend, Escalate, Reassign, Override Routing Slip, Request Info, Submit Info, and Withdraw. Custom actions are outcome that are explicitly defined by the developer in the task configuration like SUBMIT, APPROVE, REJECT, and etc.
System actions can be retrieved from the the task object as follows: "task.getSystemAttributes().getSystemActions();". But the resulting list also includes some internal keys that determines the access rights that the user can do with that specific task. These internal keys should not be presented as menu items in the Actions menu so we will exclude it from the kist of system actions.
...
    private static List<String> excludedActions;
    private List<ActionType> systemActions;
    private List<ActionType> customActions;
...
    static {
        excludedActions = new ArrayList<String>();
        excludedActions.add("UPDATE_COMMENT");
        excludedActions.add("VIEW_SUB_TASKS");
        excludedActions.add("UPDATE");
        excludedActions.add("UPDATE_ATTACHMENT");
        excludedActions.add("SUSPEND_TIMERS");
 excludedActions.add("VIEW_PROCESS_HISTORY");
        excludedActions.add("VIEW_TASK");
        excludedActions.add("CUSTOM");
        excludedActions.add("VIEW_TASK_HISTORY");
    }
    public List<ActionType> getSystemActions() throws WorkflowException {
        if (systemActions == null) {
            systemActions = new ArrayList<ActionType>();
            for (Object obj :
                 getTask().getSystemAttributes().getSystemActions()) {
                ActionType actionType = (ActionType)obj;
                if (!excludedActions.contains(actionType.getAction())) {
                    systemActions.add(actionType);
                }
            }
        }
        return systemActions;
    }
    public List<ActionType> getCustomActions() throws WorkflowException {
        if (customActions == null) {
            customActions = getTask().getSystemAttributes().getCustomActions();
        }
        return customActions;
    }
...
Some usual Actions like Submit, Approve, Reject, Claim, Acknowledge, Resume will be nicely presented as buttons if available, so by using a dummy map, we created a facility to check action availability through some EL expressions like:
<af:commandToolbarButton text="SUBMIT"
                            action="#{viewScope.taskDetailsForm.processAction}"
                            partialSubmit="false"
                            visible="#{viewScope.taskDetailsForm.actionAvailable['SUBMIT']}"
                            id="ctb1">
...
    private Map dummyMap;
    private List<String> allActions;
...
    public Map getActionAvailable() throws WorkflowException {
        if (dummyMap == null) {
            if (allActions == null) {
                allActions = new ArrayList<String>();
                for (Object obj :
                     getTask().getSystemAttributes().getCustomActions()) {
                    ActionType actionType = (ActionType)obj;
                    allActions.add(actionType.getAction());
                }
                for (Object obj :
                     getTask().getSystemAttributes().getSystemActions()) {
                    ActionType actionType = (ActionType)obj;
                    allActions.add(actionType.getAction());
                }
            }
            dummyMap = new HashMap() {
                    public Object get(Object obj) {
                        return allActions.contains(obj);
                    }
                };
        }
        return dummyMap;
    }
Actions are then processed as follows:
public String processAction() {
        try {
            updatePayload();
            if ((isCommentsRequired(action)) && (!isNewComment())) {
                raiseErrorMessage("MESSAGE_EMPTY_COMMENTS_FOR_ACTION");
                return null;
            }
            if ("REASSIGN".equals(action)) {
                ADFUtils.showDialog(reassignPopup);
                return null;
            }
            if ("INFO_REQUEST".equals(action)) {
                ADFUtils.showDialog(requestInfoPopup);
                return null;
            }
            ITaskService taskService =
                getWorkflowServiceClient().getTaskService();

            if ("UPDATE".equals(action)) {
                task = taskService.updateTask(ctx, task);
                return null;
            }
            if ("ESCALATE".equals(action)) {
                taskService.escalateTask(ctx, task);
                return "done";
            }
            if ("WITHDRAW".equals(action)) {
                taskService.withdrawTask(ctx, task);
                return "done";
            }
            if ("SUSPEND".equals(action)) {
                taskService.suspendTask(ctx, task);
                return "done";
            }
            if ("RESUME".equals(action)) {
                task = taskService.resumeTask(ctx, task);
                return null;
            }
            if ("PURGE".equals(action)) {
                taskService.purgeTask(ctx, task);
                return "done";
            }
            if ("CLAIM".equals(action)) {
                task = taskService.acquireTask(ctx, task);
                return null;
            }
            if ("INFO_SUBMIT".equals(action)) {
                if ((!isNewComment()) && (!isTaskUpdatedWithComments())) {
                    raiseErrorMessage("MESSAGE_EMPTY_COMMENTS_FOR_SUBMIT_INFO");
                    return null;
                }
                taskService.submitInfoForTask(ctx, task);
            }
            taskService.updateTaskOutcome(ctx, task, action);
            return "done";
        } catch (Exception e) {
            JSFUtils.addMessage(null, e.getMessage(), e.getMessage(),FacesMessage.SEVERITY_ERROR);
            return null;
        }
    }

Adding Comments

The list of comments can be accessed through the following EL -"#{viewScope.taskDetailsForm.task.userComment}". Below is the code snippet for the comments table:
<af:table verticalGridVisible="false"
        horizontalGridVisible="false"
        value="#{viewScope.taskDetailsForm.task.userComment}"
        var="row"
        rows="#{viewScope.taskDetailsForm.userCommentSize}"
        emptyText="#{resources.NO_ROWS_YET}"
        rowSelection="none" immediate="true"
        contentDelivery="immediate"
        inlineStyle="border-width:1px;height:10em;width:95%;"
        id="cmTab" columnStretching="last"
        summary="resources.COMMENTS_FOR_WORKLIST_TASK"
        binding="#{viewScope.taskDetailsForm.userCommentTable}">
  <af:column noWrap="false" separateRows="true"
           headerText="#{null}" rowHeader="unstyled"
           id="c2">
    <af:panelGroupLayout layout="horizontal" id="pgl11">
      <af:outputText value="#{row.updatedDate}"
                   truncateAt="9999"
                   inlineStyle="font-weight:bold"
                   id="ot2">
        <f:convertDateTime type="#{pageFlowScope.dt}"
                         timeZone="#{pageFlowScope.tz}"
                         dateStyle="#{pageFlowScope.df}"
                         timeStyle="#{pageFlowScope.tf}"
                         pattern="#{pageFlowScope.dateTimePattern}"/>
      </af:outputText>
      <af:spacer width="12" id="s9"/>
      <af:outputText value="#{row.updatedBy.id}"
                   truncateAt="9999"
                   inlineStyle="font-weight:bold"
                   id="ot4"/>
    </af:panelGroupLayout>
    <af:outputText value="#{row.comment}"
                 truncateAt="9999" id="ot8"/>
  </af:column>
</af:table>
The code snippet for the popup that facilitates adding of comments is as follows:
<af:popup id="popupAddCommentDialog"
                        contentDelivery="lazyUncached">
    <af:dialog title="#{resources.CREATE_COMMENT}" type="okCancel"
                modal="true" id="cmtDlg"
                affirmativeTextAndAccessKey="#{resources.OK_AK}"
                cancelTextAndAccessKey="#{resources.CANCEL_AK}"
                dialogListener="#{viewScope.taskDetailsForm.handleAddCommentDialogReturn}">
       <af:panelGroupLayout id="pgl12">
          <af:panelFormLayout id="pfl10">
             <f:facet name="footer"/>
                <af:inputText id="cmtBox" rows="3" columns="80"
                         value="#{viewScope.taskDetailsForm.comment}"
                         label="#{resources.COMMENT}" required="true"
                         maximumLength="2000"/>
                <af:selectOneRadio value="#{viewScope.taskDetailsForm.commentScope}"
                         label="#{resources.UPLOAD_TO_PROCESS}"
                         id="uploadToProcComment"
                         layout="vertical">
                <af:selectItem label="#{resources.TASK}" value="TASK"
                         id="si11"/>
                <af:selectItem label="#{resources.BPM}" value="BPM"
                         id="si21"/>
            </af:selectOneRadio>
         </af:panelFormLayout>
       </af:panelGroupLayout>
    </af:dialog>
  </af:popup>
The following code snippet handles the addition of comments. Just like the standard task form, comments are not persisted until the user invokes an action like save, submit, approve, etc.
...
    private String comment;
    private String commentScope = "BPM";
...
    public void handleAddCommentDialogReturn(DialogEvent dialogEvent) throws WorkflowException {
        if (dialogEvent.getOutcome() == DialogEvent.Outcome.ok) {
            ObjectFactory factory = new ObjectFactory();
            CommentType commentType = factory.createCommentType();
            commentType.setComment(comment);
            commentType.setIsSystemComment(false);
            commentType.setUpdatedDate(Calendar.getInstance());
            commentType.setCommentScope(commentScope);
            getTask().addUserComment(commentType);
            comment = "";
            AdfFacesContext.getCurrentInstance().addPartialTarget(userCommentTable);
        }
    }

Uploading and Downloading Attachments

The list of attachments can be accessed through the following EL -"#{viewScope.taskDetailsForm.task.attachment}". The ReadAttachmentBean available on JDeveloper-generated task forms works with our custom task form. Below is the code snippet for the attachments table:
<af:table verticalGridVisible="false"
            horizontalGridVisible="false"
            value="#{viewScope.taskDetailsForm.task.attachment}"
            var="row"
            rows="#{viewScope.taskDetailsForm.attachmentSize}"
            emptyText="#{resources.NO_ROWS_YET}"
            inlineStyle="width:95%; height:10em; border-width:1px;"
            id="attTb"
            partialTriggers="::dlAttBt ::adAtBt2 ::attCan ::adAtBt3 ::delAtOk"
            columnStretching="last"
            summary="#{resources.ATTACHMENTS_FOR_WORKLIST_TASK}"
            contentDelivery="immediate"
            rowSelection="single"
            selectionListener="#{viewScope.taskDetailsForm.makeCurrent}">
    <af:column noWrap="false" headerText="#{resources.NAME}"
               rowHeader="unstyled" id="c1">
      <af:panelGroupLayout id="pgl17">
        <af:switcher facetName="#{row.attachmentScope eq 'BPM'}"
                     id="s16">
          <f:facet name="true">
            <af:switcher facetName="#{row.updatedBy eq pageFlowScope.ADFHumanTaskBean.user}"
                         id="s3">
              <f:facet name="false">
                <af:goImageLink text="#{row.name}"
                                destination="#{pageFlowScope.readAttachmentBean.URL}"
                                icon="/hw_images/bpm_icon_16x16.png"
                                shortDesc="#{resources.CANNOT_DEL_ATTACH}"
                                targetFrame="_blank"
                                id="gil2"/>
              </f:facet>
              <f:facet name="true">
                <af:goImageLink text="#{row.name}"
                                destination="#{pageFlowScope.readAttachmentBean.URL}"
                                icon="/hw_images/bpm_icon_16x16.png"
                                targetFrame="_blank"
                                id="gilx1"/>
              </f:facet>
            </af:switcher>
          </f:facet>
          <f:facet name="false">
            <af:goImageLink text="#{row.name}"
                            destination="#{pageFlowScope.readAttachmentBean.URL}"
                            icon="/hw_images/humantask_ena.png"
                            targetFrame="_blank" id="gil1"/>
          </f:facet>
        </af:switcher>
      </af:panelGroupLayout>
    </af:column>
    <af:column noWrap="false"
               headerText="#{resources.UPDATED_BY}"
               rowHeader="unstyled" id="uTypeu">
      <af:panelGroupLayout id="uPgTypeu">
        <af:outputText value="#{row.updatedBy}"
                       id="uFieldu"/>
      </af:panelGroupLayout>
    </af:column>
    <af:column noWrap="false"
               headerText="#{resources.DATE_UPDATED}"
               rowHeader="unstyled" id="udTypeu">
      <af:panelGroupLayout id="udPgTypeu">
        <af:outputText value="#{row.updatedDate}"
                       id="udFieldu"/>
      </af:panelGroupLayout>
    </af:column>
  </af:table>
The following snippet is the attachment popup. We remove support for UCM for the moment.
<af:popup id="popupAddAttachmentDialog"
            contentDelivery="lazyUncached"
            binding="#{viewScope.taskDetailsForm.attachmentPopup}">
    <af:dialog title="#{resources.ADD_ATTACHMENT}" type="none"
               id="d2">
      <f:facet name="buttonBar">
        <af:panelGroupLayout id="pgl14">
          <af:commandButton id="adAtBt2"
                            textAndAccessKey="#{resources.OK_AK}"
                            visible="#{viewScope.taskDetailsForm.selectedAttachmentType == 'url'}"
                            partialTriggers="edAttTy"
                            partialSubmit="true"
                  action="#{viewScope.taskDetailsForm.addURLAttachment}"
                            actionListener="#{viewScope.taskDetailsForm.hideAttachmentDialog}"/>
          <af:commandButton id="adFAtBt"
                            textAndAccessKey="#{resources.OK_AK}"
                            action="#{viewScope.taskDetailsForm.uploadFile}"
                            actionListener="#{viewScope.taskDetailsForm.hideAttachmentDialog}"
                            visible="#{viewScope.taskDetailsForm.selectedAttachmentType == 'file'}"
                            partialSubmit="true"
                            partialTriggers="edAttTy"/>
          <af:commandButton id="attCan"
                            textAndAccessKey="#{resources.CANCEL_AK}"
                            action="#{viewScope.taskDetailsForm.clearUploadedFile}"
                            immediate="true" partialSubmit="true"
                            partialTriggers="attTb"/>
        </af:panelGroupLayout>
      </f:facet>
      <af:panelGroupLayout id="pgl7">
        <f:facet name="separator">
          <af:spacer width="15" height="15" id="s2"/>
        </f:facet>
        <af:panelFormLayout id="pfl1">
          <af:selectOneRadio value="#{viewScope.taskDetailsForm.attachmentScope}"
                             label="#{resources.UPLOAD_TO_PROCESS}"
                             id="uploadToProcAttach"
                             layout="vertical">
            <af:selectItem label="#{resources.TASK}" value="TASK"
                           id="si14"/>
            <af:selectItem label="#{resources.BPM}" value="BPM"
                           id="si25"/>
          </af:selectOneRadio>
          <af:spacer width="10" height="10" id="s10"/>
          <af:selectOneRadio label="#{resources.ATTACH_TYPE}"
                             value="#{viewScope.taskDetailsForm.selectedAttachmentType}"
                             valueChangeListener="#{viewScope.taskDetailsForm.toggleAttachmentType}"
                             autoSubmit="true" id="edAttTy"
                             immediate="true" required="true"
                             layout="vertical">
            <af:selectItem label="#{resources.URL}" value="url"
                           id="si1"/>
            <af:selectItem label="#{resources.DESKTOP_FILE}"
                           value="file"
                           id="si4"/>
          </af:selectOneRadio>
        </af:panelFormLayout>
        <af:panelGroupLayout layout="vertical"
                             partialTriggers="edAttTy" id="pgl4">
          <af:switcher facetName="#{viewScope.taskDetailsForm.selectedAttachmentType}"
                       id="s5">
            <f:facet name="url">
              <af:panelFormLayout id="pfl11">
                <af:inputText id="attName" label="#{resources.NAME}"
                              value="#{viewScope.taskDetailsForm.URIname}"
                              required="true"
                              partialTriggers="edAttTy"
                              maximumLength="128"/>
                <af:inputText id="attHref" label="#{resources.URL}"
                              value="#{viewScope.taskDetailsForm.URI}"
                              required="true"
                              partialTriggers="edAttTy"
                              maximumLength="256"/>
              </af:panelFormLayout>
            </f:facet>
            <f:facet name="file">
              <af:panelGroupLayout layout="vertical" id="pg919">
                <af:panelFormLayout id="pfl2">
                  <af:inputFile label="#{resources.FILE_NAME}"
                                value="#{viewScope.taskDetailsForm.file}"
                                id="attFile" required="true"
                                partialTriggers="edAttTy"
                                binding="#{viewScope.taskDetailsForm.richFile}"/>
                  <af:inputText label="#{resources.DESCRIPTION}"
                                value="#{viewScope.taskDetailsForm.docComments}"
                                rows="4" id="it5"/>
                </af:panelFormLayout>
                <af:spacer height="10" width="10" id="s15"/>
              </af:panelGroupLayout>
            </f:facet>
          </af:switcher>
        </af:panelGroupLayout>
      </af:panelGroupLayout>
    </af:dialog>
  </af:popup>
The following code snippets supports the uploading of URL and desktop file attachments.
...
    public void clearUploadedFile() {
        richFile.setSubmittedValue(null);
        ADFUtils.closeDialog(attachmentPopup);
        RequestContext.getCurrentInstance().addPartialTarget(richFile);
    }
    
    public void addURLAttachment() throws Exception {
        AttachmentType attachmentType = createAttachment();
        attachmentType.setName(getURIname());
        attachmentType.setURI(getURI());
        attachmentType.setTitle(getURIname());
        getTask().addAttachment(attachmentType);
    }

    public void uploadFile() throws Exception {
        AttachmentType attachmentType = createAttachment();
        UploadedFile file = getFile();
        attachmentType.setName(file.getFilename());
        attachmentType.setMimeType(file.getContentType());
        attachmentType.setInputStream(file.getInputStream());
        attachmentType = uploadFile(attachmentType);
        getTask().addAttachment(attachmentType);
    }

    private AttachmentType createAttachment() {
        AttachmentType attachmentType =
            new ObjectFactory().createAttachmentType();
        String taskId =
            (String)ADFUtils.getPageFlowScope().get("bpmWorklistTaskId");
        String taskVersionStr =
            (String)ADFUtils.getPageFlowScope().get("bpmWorklistTaskVersion");
        attachmentType.setTaskId(taskId);
        attachmentType.setVersion(getSafeInt(taskVersionStr));
        attachmentType.setAttachmentScope(getAttachmentScope());
        String comment = getDocComments();
        if ((comment != null) && (!comment.equals("")))
            attachmentType.setDescription(comment);
        return attachmentType;
    }

    private AttachmentType uploadFile(AttachmentType attachmentType) throws Exception {
        String fileName = attachmentType.getName();
        IWorkflowServiceClient wfSvcClient =
            getWorkflowServiceClient();
        IWorkflowContext context = getWorkflowContext();
        String taskString=  WorkflowAttachmentUtil.uploadAttachment(context, wfSvcClient,
                                                       attachmentType, null);
        Task task = (Task)new ObjectFactory().unmarshal(XMLUtil.parseDocumentFromXMLString(taskString).getDocumentElement());
        List attachments = task.getAttachment();
        for (Object obj : attachments) {
            AttachmentType taskAttachment = (AttachmentType)obj;
            if (fileName.equals(taskAttachment.getName())) {
                attachmentType = taskAttachment;
            }
        }
        return attachmentType;
    }
...

Task Reassignment

The following is the code snippet for the popup that displays an identity browser. We reuse the IdentityBrowserView and TaskFlowReassignBean managed beans.
<af:popup id="reassignPopup" contentDelivery="lazyUncached"
            binding="#{taskDetailsForm.reassignPopup}">
    <af:dialog id="reAsDg"
               cancelTextAndAccessKey="#{resources.CANCEL_AK}"
               affirmativeTextAndAccessKey="#{resources.OK_AK}"
               title="#{resources.REASSIGNPAGE}"
               dialogListener="#{taskDetailsForm.handleReassignPopupDialogReturn}">
   
      <wlc:identityBrowser identityBrowserView="#{pageFlowScope.identityBrowserView}"
                           workflowContext="#{taskDetailsForm.workflowContext}"
                           id="reAsIdB" mode="REASSIGN"
                           showActionButtons="false"
                           selectedTaskIds="#{pageFlowScope.taskFlowReassignBean.selectedTaskIds}"/>
  
    </af:dialog>
  </af:popup>
public void handleReassignPopupDialogReturn(DialogEvent ev) throws Exception {
        if (DialogEvent.Outcome.ok.equals(ev.getOutcome())) {
            IdentityBrowserView identityBrowserView = getIdentityBrowserView();
            List selectedIdentities =
                identityBrowserView.getSelectedIdentities();
            List<ITaskAssignee> assignees = getSelectedIdentitiesFromIdentityBrowser(selectedIdentities);
            ITaskService taskService =
                getWorkflowServiceClient().getTaskService();
            taskService.reassignTask(ctx, task, assignees);
            JSFUtils.handleNavigation(null, "done");
        }
    }
    //borrowed methods
    public static List getSelectedIdentitiesFromIdentityBrowser(List selectedIdentities) {
        List assignees = new ArrayList();
        int num = selectedIdentities.size();
        for (int i = 0; i < num; i++) {
            String name = (String)selectedIdentities.get(i);
            String idName = name.substring(0, name.lastIndexOf(95));
            String identityType = name.substring(name.lastIndexOf(95) + 1);

            if (identityType.equals("user")) {
                assignees.add(new TaskAssignee(idName, "user"));
            } else if (identityType.equals("group")) {
                assignees.add(new TaskAssignee(idName, "group"));
            } else if (identityType.equals("approle")) {
                assignees.add(new TaskAssignee(idName, "application_role"));
            }
        }
        return assignees;
    }

Request Information

<af:popup id="requestInfoPopup" contentDelivery="lazyUncached"
            binding="#{taskDetailsForm.requestInfoPopup}">
    <af:dialog title="#{resources.INFOREQUESTEDPAGE}"
               cancelTextAndAccessKey="#{resources.CANCEL_AK}"
               affirmativeTextAndAccessKey="#{resources.OK_AK}"
               dialogListener="#{taskDetailsForm.handleRequestInfoPopupDialogReturn}"
               id="reqIfD">
        
      <wlc:requestInfoDialog workflowContext="#{taskDetailsForm.workflowContext}"
                             id="rqIDgC"
                             selectedTaskId="#{pageFlowScope.taskFlowReassignBean.selectedTaskIds[0]}"/>
        
    </af:dialog>
  </af:popup>
public void handleRequestInfoPopupDialogReturn(DialogEvent ev) throws Exception {
        if (DialogEvent.Outcome.ok.equals(ev.getOutcome())) {
            RequestInfoView requestInfoView =
                (RequestInfoView)ADFUtils.getPageFlowScope().get("requestInfoView");
            RequestInfoModel requestInfoModel =
                requestInfoView.getRequestInfoModel();
            RequestInfoVO requestInfoVO = requestInfoModel.getRequestInfoVO();
            String comment = requestInfoVO.getComments();
            addComment(comment, "BPM");
            String identityType = requestInfoVO.getOtherUserIdentityType();
            String requestUser = requestInfoVO.getRequestUser();
            boolean reapprovalNeeded = requestInfoVO.isIsReapprovalNeeded();
            ITaskAssignee assignee = null;
            if (identityType == null) {
                assignee = new TaskAssignee(requestUser, "user");
            } else {
                assignee = new TaskAssignee(requestUser, identityType);
            }
            ITaskService taskService =
                getWorkflowServiceClient().getTaskService();
            if (reapprovalNeeded) {
                taskService.requestInfoForTaskWithReapproval(ctx, task,
                                                             assignee);
            } else {
                taskService.requestInfoForTask(ctx, task, assignee);
            }
            JSFUtils.handleNavigation(null, "done");
        }
    }

Creating the Task Flow Wrapper Template

To simplify the usage of this reusable task form, I created a task flow template as follows:

Wrapper_template.xml
<?xml version="1.0" encoding="windows-1252" ?>
<adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
  <task-flow-template id="Wrapper_template">
    <default-activity id="__1">human-task-flow</default-activity>
    <input-parameter-definition id="__17">
      <name id="__18">bpmWorklistContext</name>
      <value>#{pageFlowScope.bpmWorklistContext}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__19">
      <name id="__20">bpmWorklistTaskId</name>
      <value>#{pageFlowScope.bpmWorklistTaskId}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__21">
      <name id="__22">bpmWorklistTaskVersion</name>
      <value>#{pageFlowScope.bpmWorklistTaskVersion}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__26">
      <name id="__25">bpmWorklistHttpURL</name>
      <value>#{pageFlowScope.bpmWorklistHttpURL}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__37">
      <name id="__36">bpmWorklistRequestInfo</name>
      <value>#{pageFlowScope.bpmWorklistRequestInfo}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__39">
      <name id="__38">bpmWorklistHome</name>
      <value>#{pageFlowScope.bpmWorklistHome}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__40">
      <name id="__41">bpmWorklistLogin</name>
      <value>#{pageFlowScope.bpmWorklistLogin}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__43">
      <name id="__42">bpmWorklistLogout</name>
      <value>#{pageFlowScope.bpmWorklistLogout}</value>
    </input-parameter-definition>
    <input-parameter-definition id="__33">
      <name id="__35">bpmWorklistReassign</name>
      <value id="__34">#{pageFlowScope.bpmWorklistReassign}</value>
      <class id="__336"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__339">
      <name id="__338">bpmWorklistHistory</name>
      <value id="__340">#{pageFlowScope.bpmWorklistHistory}</value>
      <class id="__437"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__47">
      <name id="__46">bpmWorklistRoute</name>
      <value id="__45">#{pageFlowScope.bpmWorklistRoute}</value>
      <class id="__48"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__52">
      <name id="__49">bpmClientType</name>
      <value id="__50">#{pageFlowScope.bpmClientType}</value>
      <class id="__51"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__53">
      <name id="__56">bpmBrowserWindowStatus</name>
      <value id="__55">#{pageFlowScope.bpmBrowserWindowStatus}</value>
      <class id="__54"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__57">
      <name id="__59">bpmWorklistBackPage</name>
      <value id="__60">#{pageFlowScope.bpmWorklistBackPage}</value>
      <class id="__58"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__64">
      <name id="__62">bpmWorklistSecurity</name>
      <value id="__63">#{pageFlowScope.bpmWorklistSecurity}</value>
      <class id="__61"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__67">
      <name id="__66">bpmWorklistGraphHistory</name>
      <value id="__65">#{pageFlowScope.bpmWorklistGraphSecurity}</value>
      <class id="__68"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__71">
      <name id="__69">lg</name>
      <value id="__70">#{pageFlowScope.lg}</value>
      <class id="__72"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__76">
      <name id="__75">cy</name>
      <value id="__74">#{pageFlowScope.cy}</value>
      <class id="__73"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__80">
      <name id="__78">vr</name>
      <value id="__77">#{pageFlowScope.vr}</value>
      <class id="__79"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__83">
      <name id="__82">tz</name>
      <value id="__84">#{pageFlowScope.tz}</value>
      <class id="__81"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__88">
      <name id="__87">df</name>
      <value id="__86">#{pageFlowScope.df}</value>
      <class id="__85"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__90">
      <name id="__89">tf</name>
      <value id="__92">#{pageFlowScope.tf}</value>
      <class id="__91"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__94">
      <name id="__93">dt</name>
      <value id="__95">#{pageFlowScope.dt}</value>
      <class id="__96"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__98">
      <name id="__97">dateTimePattern</name>
      <value id="__99">#{pageFlowScope.dateTimePattern}</value>
      <class id="__100"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__101">
      <name id="__103">datePattern</name>
      <value id="__102">#{pageFlowScope.datePattern}</value>
      <class id="__104"></class>
    </input-parameter-definition>
    <input-parameter-definition id="__106">
      <name id="__108">timePattern</name>
      <value id="__107">#{pageFlowScope.timePattern}</value>
      <class id="__105"></class>
    </input-parameter-definition>
    <task-flow-call id="human-task-flow">
      <task-flow-reference>
        <document>/WEB-INF/soadev/bpm/human-task-flow.xml</document>
        <id>human-task-flow</id>
      </task-flow-reference>
      <input-parameter id="__2">
        <name>bpmWorklistContext</name>
        <value>#{pageFlowScope.bpmWorklistContext}</value>
      </input-parameter>
      <input-parameter id="__3">
        <name>bpmWorklistTaskId</name>
        <value>#{pageFlowScope.bpmWorklistTaskId}</value>
      </input-parameter>
      <input-parameter id="__4">
        <name>bpmWorklistTaskVersion</name>
        <value>#{pageFlowScope.bpmWorklistTaskVersion}</value>
      </input-parameter>
      <input-parameter id="__5">
        <name>bpmWorklistHttpURL</name>
        <value>#{pageFlowScope.bpmWorklistHttpURL}</value>
      </input-parameter>
      <input-parameter id="__6">
        <name>bpmWorklistRequestInfo</name>
        <value>#{pageFlowScope.bpmWorklistRequestInfo}</value>
      </input-parameter>
      <input-parameter id="__7">
        <name>bpmWorklistHome</name>
        <value>#{pageFlowScope.bpmWorklistHome}</value>
      </input-parameter>
      <input-parameter id="__8">
        <name>bpmWorklistLogin</name>
        <value>#{pageFlowScope.bpmWorklistLogin}</value>
      </input-parameter>
      <input-parameter id="__9">
        <name>bpmWorklistLogout</name>
        <value>#{pageFlowScope.bpmWorklistLogout}</value>
      </input-parameter>
      <input-parameter id="__10">
        <name>bpmWorklistReassign</name>
        <value>#{pageFlowScope.bpmWorklistReassign}</value>
      </input-parameter>
      <input-parameter id="__11">
        <name>bpmWorklistHistory</name>
        <value>#{pageFlowScope.bpmWorklistHistory}</value>
      </input-parameter>
      <input-parameter id="__12">
        <name>bpmWorklistRoute</name>
        <value>#{pageFlowScope.bpmWorklistRoute}</value>
      </input-parameter>
      <input-parameter id="__13">
        <name>bpmClientType</name>
        <value>#{pageFlowScope.bpmClientType}</value>
      </input-parameter>
      <input-parameter id="__14">
        <name>bpmBrowserWindowStatus</name>
        <value>#{pageFlowScope.bpmBrowserWindoStatus}</value>
      </input-parameter>
      <input-parameter id="__15">
        <name>bpmWorklistBackPage</name>
        <value>#{pageFlowScope.bpmWorklistBackPage}</value>
      </input-parameter>
      <input-parameter id="__16">
        <name>bpmWorklistSecurity</name>
        <value>#{pageFlowScope.bpmWorklistSecurity}</value>
      </input-parameter>
      <input-parameter id="__23">
        <name>bpmWorklistGraphHistory</name>
        <value>#{pageFlowScope.bpmWorklistGraphHistory}</value>
      </input-parameter>
      <input-parameter id="__24">
        <name>lg</name>
        <value>#{pageFlowScope.lg}</value>
      </input-parameter>
      <input-parameter id="__27">
        <name>cy</name>
        <value>#{pageFlowScope.cy}</value>
      </input-parameter>
      <input-parameter id="__28">
        <name>vr</name>
        <value>#{pageFlowScope.vr}</value>
      </input-parameter>
      <input-parameter id="__29">
        <name>tz</name>
        <value>#{pageFlowScope.tz}</value>
      </input-parameter>
      <input-parameter id="__30">
        <name>df</name>
        <value>#{pageFlowScope.df}</value>
      </input-parameter>
      <input-parameter id="__31">
        <name>tf</name>
        <value>#{pageFlowScope.tf}</value>
      </input-parameter>
      <input-parameter id="__32">
        <name>dt</name>
        <value>#{pageFlowScope.dt}</value>
      </input-parameter>
      <input-parameter id="__109">
        <name>dateTimePattern</name>
        <value>#{pageFlowScope.dateTimePattern}</value>
      </input-parameter>
      <input-parameter id="__110">
        <name>datePattern</name>
        <value>#{pageFlowScope.datePattern}</value>
      </input-parameter>
      <input-parameter id="__111">
        <name>timePattern</name>
        <value>#{pageFlowScope.timePattern}</value>
      </input-parameter>
      <input-parameter id="__724">
        <name>payloadHandler</name>
        <value>#{pageFlowScope.payloadHandler}</value>
      </input-parameter>
      <input-parameter id="__727">
        <name>payloadTaskFlowId</name>
        <value>#{pageFlowScope.payloadTaskFlowIdBean.payloadTaskFlowId}</value>
      </input-parameter>
    </task-flow-call>
    <task-flow-return id="taskFlowReturn">
      <outcome id="__992">
        <name>done</name>
      </outcome>
    </task-flow-return>
    <control-flow-rule id="__770">
      <from-activity-id id="__769">*</from-activity-id>
      <control-flow-case id="__771">
        <from-outcome id="__773">done</from-outcome>
        <to-activity-id id="__772">taskFlowReturn</to-activity-id>
      </control-flow-case>
    </control-flow-rule>
    <visibility id="__44">
      <url-invoke-allowed/>
    </visibility>
  </task-flow-template>
</adfc-config>
With the task flow template above, to create the appropriate wrapper task flow is as simple as the following:

HelloWorld_TF.xml
<?xml version="1.0" encoding="windows-1252" ?>
<adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
  <task-flow-definition id="HelloWorld_TF">
    <template-reference>
      <document>/WEB-INF/templates/Wrapper_template.xml</document>
      <id id="___3">Wrapper_template</id>
    </template-reference>
    <managed-bean id="__1">
      <managed-bean-name id="__4">payloadHandler</managed-bean-name>
      <managed-bean-class id="__2">soadev.bpm.view.helper.HelloWorldPayloadHandler</managed-bean-class>
      <managed-bean-scope id="__3">pageFlow</managed-bean-scope>
    </managed-bean>
    <managed-bean id="__8">
      <managed-bean-name id="__7">payloadTaskFlowIdBean</managed-bean-name>
      <managed-bean-class id="__6">soadev.bpm.view.managed.PayloadTaskFlowIdBean</managed-bean-class>
      <managed-bean-scope id="__5">pageFlow</managed-bean-scope>
      <managed-property id="__10">
        <property-name id="__9">payloadTaskFlowId</property-name>
        <value id="__11">/WEB-INF/HelloWorld-payload-task-flow.xml#HelloWorld-payload-task-flow</value>
      </managed-property>
    </managed-bean>
  </task-flow-definition>
</adfc-config>
Since task flow templates does not support attributes, we passed the payload task flow Id as a managed property to a managed bean.

Summary

In this post, I have described the key Human Workflow APIs and the important artifacts that I have arrived to support the reusable task form based on Java. In Part 3, I will demonstrate the convenience of using this pattern by deploying the reusable task form as an ADF library, and showing a usage that incorporates programmatic manipulation of task payload.

Sample Application

I have tested the sample application in an installation of BPM 11.1.1.5 Feature Pack. The sample can be downloaded from here.


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.