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.


5 comments:

  1. the sample download link got 404 error

    ReplyDelete
  2. Pls add the sample application....It would be of great help if you would do so...

    ReplyDelete
  3. Hi,
    The sample can now be cloned from my github repo. https://github.com/rommelpino/bpmtaskform

    ReplyDelete
  4. Hi Rommel..

    Thanks for uploading the project.Can you please add your ADFutils and JSFutils classes.They are throwing error

    Regards...

    ReplyDelete
  5. The related JSFUtils and ADFUtils can be found on the Utils project on the following repo:
    https://github.com/rommelpino/ejbquerylov/tree/master/Utils/src/soadev/view/utils

    ReplyDelete