Updating BPM 11g XML Payload From ADF

Oracle BPM 11g (or BPEL)  uses XML to pass along process payload information.  To modify this information in a UI, BPM data controls are typically used to update this data from the ADF user interface. This is a well known technique and easy to do.  These data controls can be generated from the human task UI wizard or through the new gallery's "ADF Task Flow Based on Human Task", Figure 1.  So long as the UI uses the BPM's data controls, all is well.

ADF Task Flow Based on Human Task

Figure 1. Creating Task Flow from BPM Human Task

But using the BPM data controls (Figure 2) isn't always what we need, is it?  Many times the UI content doesn't originate from BPM; only small portions of the data reside in the BPM payload.  In many cases it comes from a data base, POJOs, or web services.  Here the UI forms are not tied to BPM data, but something else.  When this happens we may still want to update the BPM payload, even though the UI forms and data controls update some other back-end system and when this happens we need a way to update the BPM payload.  It is a little tricky, but not diffcult, if you know how.

BPM Data Controls

Figure 2. BPM Data Controls

To implement a UI not tied to the BPM data controls, there are two things you need to do.  The first is to write some Java code to get and update BPM's payload data. This is done using a managed bean called Task tied to the task-flow.  The code you need is contained below, but this is the easy part.  The tricky part, and second thing we need to do, is to intercept the call to the BPM submit operations, like Accept or Reject.  We need to intercept this call so changes can be made to the BPM payload before control is passed back to the BPM process.  If you try it in the task-flow after someone clicks on one of the BPM linked <af:commandToolbarButton>, it will be too late.  So I will show you how to intercept this call at the right time. 

But before I show you this tricky part, let me give you some code you'll need to get the BPM Task managed bean and and show you how to modify the payload information. First the import statements: 

import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.datacontrol.WorkflowService;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.task.ITaskService;
import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.verification.IWorkflowContext;

And now the code.  This code is in a method called modifyBPMPayloadData. It gets the active Task object from the Java Faces task-flow context:       

void modifyBPMPayloadData() {

FacesContext context = FacesContext.getCurrentInstance();
String tskId =  (String)context.getApplication().evaluateExpressionGet(context,
                                                    "#{pageFlowScope.bpmWorklistTaskId}",  String.class);
IWorkflowServiceClient workflowSvcClient =   WorkflowService.getWorkflowServiceClient();
ITaskService taskSvc =
workflowSvcClient .getTaskService();
ITaskQueryService wfQueryService =  workflowSvcClient.getTaskQueryService();
IWorkflowContext wfContext =   WorklistUtil.getWorkflowContextForASelectedTask();
Task myTask = wfQueryService.getTaskDetailsById(wfContext, tskId);

Element payloadElem = myTask.getPayloadAsElement()

/*
Put your code in here to modify BPM payload data
*/

myTask.setPayloadAsElement(payloadElem);
taskSvc .updateTask(wfContext ,  task);

}

Let's break down this Java method.  The code up to "wfQueryService.getTaskDetailsById" is needed to locate the instance of the Task object and the BPM payload data.  Once that is done, there are two useful methods on the Task Class;

Element payloadElem = myTask.getPayloadAsElement()
myTask.setPayloadAsElement(payloadElem);

You can use these methods to get the BPM payload data, modify it, and place it back into the Task instance.  After you set the payload back on the Task, all you have to do is notify the workflow context that the data has changed, as shown here:

workflowSvcClient.updateTask(wfContext ,  task);

Okay, now you should be wondering, when is this done?  And this is the tricky part I told you about.  This has to be done before control is passed back to the BPM process.  And in ADF BPM data controls, control is passed back to BPM when one of the human task action buttons are selected.  If you look for these action buttons, you see some XML statements like this one in your jspx file:

<af:commandToolbarButton

            actionListener="#{invokeActionBean.setOperation}"

            text="#{wf:getResourceValue('REJECT', 'bindings.customActions')}"

            disabled="#{!bindings.REJECT.enabled}"

            action="#{invokeActionBean.invokeOperation}"

            partialSubmit="false"

             id="ctb10">

    <f:attribute name="DC_OPERATION_BINDING”  value="bindings.REJECT"/>

</af:commandToolbarButton>

When using BPM-based data controls, these action buttons, like "Reject",  simply pass control back to your process through a web service call-back operation.  This updates the BPM payload and tells the BPM process what action was taken; e.g. "Reject".  We can intercept this call by modifying the action listener to one of our own.  This action listener will call the code that modifies the BPM payload before control is passed back to your business process. Simply modify the actionListener method to something, like this:

<af:commandToolbarButton

           actionListener="#{myClientServiceHelper.mySetOperation}"

           text="Reject"

           disabled="#{!bindings.REJECT.enabled}"

            action="#{invokeActionBean.invokeOperation}"

            partialSubmit="false"

            id="ctb2" rendered="false">

             <f:attribute name="DC_OPERATION_BINDING”  value="bindings.REJECT"/>

 </af:commandToolbarButton>

In  this exemple, myClientServiceHelper is a java bean registered with the task flow and contains a method like this one:

 public void mySetOperation(ActionEvent action) {

      FacesContext fctx = FacesContext.getCurrentInstance();
      Application app = fctx.getApplication();
      ELContext elContext = FacesContext.getCurrentInstance().getELContext();
      InvokeActionBean invokeActionBean = app.getELResolver().getValue(elContext, null,  "
 invokeActionBean");

       invokeActionBean.setOperation(action);

       modifyBPMPayloadData() ;  /* This is where you modify the BPM payload data using the code described above */

    }

 

This code acts like a proxy for the actionListener in the original call namely:

 

 actionListener="#{invokeActionBean.setOperation}"

 The mySetOperation action listener method makes this call after it locates the invokeActionBean on the Faces Context.  Incidentally the InvokeActionBean class import statement is a follows:

       import oracle.bpel.services.workflow.worklist.adf.InvokeActionBean;

Once the proxy call is complete, this actionListener method calls the modifyBPMPayloadData() method described above.  This modifies and then updates the BPM payload before the BPM call back opertation is performed and control is passed back to the BPM process.

And that's all there is. Like I said it's a bit tricky, but not difficult if you know how.

 

I hope you found this helpful.  I used this code to update BPM payload data from an XML document generated by a complex set of JAXB classes. The JAXB classes were used as data controls in a UI through a POJO facade layer.  The JAXB data was marshalled to and from XML for use in a back-end web service interface.  Let me know if you have any questions about this blog or wish to learn more about using POJOs and JAXB classes in ADF Data Controls.  I may blog about this next. I hope to hear from you soon.

Comments

NullPointerException

Hi Mark,

 

first of all, thank you very much for this post. After looking everywhere, a colleague sent me this link and it's exactly what I need but now that I'm trying it I'm getting a NullPointerException on the line

 

invokeActionBean.setOperation(action);

 

I created the bean in the Application Scope and had to fix some of the imports that didn't work for me from copy & pasting them from here but this call occurs before any of the troubles I had with the imports. It looks like

 

(InvokeActionBean)app.getELResolver().getValue(elContext, null,  " invokeActionBean");

 

is returning null for some reason. I would very much appreciate (and kind of need :)) your any thought on this.

 

Thanks a lot again!

 

regards

Re: NullPointerException

Check to be sure the invokeActionBean is a managed bean being used by the Task flow. If you manually created the task-flow you probably don't have all the BPM related beans you'll need to make this work. This blog described how to do create the task flow by using "ADF Task-flow based on Human Task" located in the New Gallery. This is an important step, even if you don't use BPM data control to support your data bindings in your UI.

 

To locate the invokeActionBean, open the task flow, click on "Overview" tab and then select "Managed Beans" tab. If you don't see the invokeActionBean in this list, then this is probably because the task flow was not created as described above.

 

Hope that helps.

Mark

NullPointerException

Hi Mark,

 

thanks for your response.

 

I created the taskflow by right-clicking on the Human Taskflow in the JDev BPM model and selecting "Auto-generate Task form" so the bean is in the Managed Beans tab. I changed the scope from request to application but I'm still getting the NullPointer. Also, shouldn't it be called after modifying the BPM Payload?

 

A workaround I tried was to just do a new InvokeActionBean() but then when I click submit, the taskflow won't end so the process gets stuck on that step. Any way to force the submission with the modified payload by this means?

 

Regards

 

Carlos

NullPointerException Fixed

Hi Mark,

 

please shoot me. When I copy and pasted your code there's a little space in

 

(InvokeActionBean)app.getELResolver().getValue(elContext, null,  " invokeActionBean");

 

Also, I changed the scope to Application. Probably has nothing to do but I've left it like that because it works and I don't really need best practices right now :)

 

thanks a lot Mark, your code example totally saved the day!

 

regards

 

p.d. allow me to paste the following code which gives an idea on how to easily manipulate the payload

 

Document document = //createXMLDocument
Element payloadElem = document.createElementNS("http://xmlns.oracle.com/bpel/workflow/task", "payload");
Element orderElem = document.createElementNS("http://xmlns.oracle.com/pcbpel/test/order", "order");
Element child = document.createElementNS("http://xmlns.oracle.com/pcbpel/test/order", "id");
child.appendChild(document.createTextNode("1234567"));
orderElem.appendChild(child);
payloadElem.appendChild(orderElem);
document.appendChild(payloadElem);

 

It's in the API but I've lost hours of research finding it and together with your lines makes for THE reference in order to modify the process' payload :)

Re: NullPointerException Fixed

Carlos - that is frustrating.  I fixed the post to removed that blasted space. Thanks for your contribution to the blog.

 

Just to comment on your question about setOperation. I thought the same thing too and tried to change the content and then call setOperation.  I can't remember why, but I do remember it didn't work. It has to done in the order shown here,  before you change the content and set it back on the task.

 

I'm glad you got it working!

 

Mark

 

updateTask Method

Hi i used your code for updating the payload from a managed bean... but i don't find this method workflowSvcClient .updateTask(wfContext ,  task) i used jdeveloper 11.1.1.5.0 with Soa Suite PS4.

 

The other question is, you have a sample project with this implementation?

 

Thanks

 


updateTask Method location

There was a missing statement in this code. Here the missing statement:


ITaskService taskSvc = workflowSvcClient .getTaskService();


And modify the update task to use taskSvc instead.

 

taskSvc.updateTask(wfContext, task);

 


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
 

Avio Consulting, LLC

5600 Tennyson Parkway

Suite 340

Plano, TX  75024

Email: info@avioconsulting.com

Phone: +1 (972) 608-4777

Twitter    Facebook