So far we've used workflow for simple task approval in conjunction with the worklist application. However, human workflows are often more complex, often involving multiple participants as well as requiring the task list to be integrated into the user's existing user interface rather than accessed through a standalone worklist application.
In this chapter, we look at these common requirements. First, we examine how to manage workflows involving complex chains of approval, including parallel approvers and the different options that are available. Next, we look at the Workflow Service API and how we can use that to completely transform the look and feel of the workflow service.
The process for validating items which have been flagged as suspicious is a classic workflow scenario that may potentially involve multiple participants.
The first step in the workflow requires an oBay administrator to check whether the item is suspect. Assuming the case is straightforward they can either approve or reject the item and complete the workflow.
This is pretty straightforward; however, for grey areas the oBay administrator needs to defer making a decision. In this scenario we have a second step in which the item is submitted to a panel who can vote on whether to approve or reject the item.
There are two approaches to model this workflow, one is to model each step as a separate Human Task, the other is to model it as a single Human Task containing multiple assignments and routing policies. Each approach has its own advantages and disadvantages, so we will look at each in turn to understand the differences.
For our checkSuspectItem
process, we are first going to take the approach of combining the two workflow steps into a single Human Task. The first step in the workflow is the familiar single approval step, where we assign the task to the oBayAdministrator
group.
The task takes a single un-editable parameter of type suspectItem
, which contains the details of the item in question as well as why it has been flagged as suspect. The definition of this is shown as follows:
<xsd:element name="suspectItem" type="act:tSuspectItem"/> <xsd:complexType name="tSuspectItem"> <xsd:sequence> <xsd:element name=" item" type="act:ItemType"/> <xsd:element name=" reasonCode" type="xsd:string" /> <xsd:element name=" reasonDesc" type="xsd:string"/> </xsd:sequence> </xsd:complexType>
For the second step in the workflow we are going to define a participant type of Group Vote; this participant type allows us to allocate the same task to multiple participants in parallel. The task definition form for a group vote is shown in the following figure:
The first requirement is to allocate this task to all users in the voting panel. To enable this, we've defined the group SuspectItemPanel
within our user repository. We don't want to allocate the task to the group, as this would only allow one user from the group to acquire and process the task. Rather we want to allocate it to all members of the group. To do that we can use the Identity Service XPath function ids:getUsersInGroup
, as illustrated here:
ids:getUsersInGroup ('SuspectItemPanel', false())
Note the second parameter is a Boolean which, if set to true
, will only return direct members of the group. Setting it to false
causes it to return all members of the group.
Doing this will effectively create and assign a separate sub-task to every member of the group.
There is an issue with this approach so far, in that the second step will always be executed regardless of what happens in the first step. To prevent this we've specified the following skip rule:
/task:task/task:systemAttributes/task:outcome != 'DEFER'
The skip rule lets you specify an XPath expression which evaluates to a boolean value. If it evaluates to true
then the corresponding participant will be skipped in the task.
In our case we are testing the outcome taken by the oBay administrator in the previous step. If they didn't defer it, that is they chose to either accept or reject the item, then this step is skipped.
When panel members are considering their decision they may want to confer with one another. By default anyone assigned a task will be able to see comments and attachments made by participants in previous steps of the task (that is, the oBay administrator), however, they won't be able to see comments made by other panel members.
To enable the sharing of attachments and comments between panel members we've selected the Share comments and attachments checkbox.
The final part of the group vote is to specify what the outcome is in the event that not all members are in agreement. There are two parts to this; the first is to specify what the default outcome is if there isn't agreement, in our case we want to REJECT
the item.
The second part is to specify what constitutes agreement, this can either be unanimous, that is, everyone must agree, or the default outcome is selected. Alternatively, you can specify that only a majority need to be in agreement and what the size of that majority should be.
The size of the majority can be a fixed amount (60% as in our case), or can be based on an XPath expression which could calculate this value dynamically at run time.
The final option you have is to specify whether all votes should be counted or if once we have sufficient votes on which to make a decision the outcome should be triggered. In this scenario, any outstanding sub-tasks will be withdrawn.
In our case the panel consists of three members, so as soon as two have approved the task, the required consensus will have been achieved and the third member will have their task withdrawn.
The other approach to this workflow is to model each step as a separate Human Task in its own right, each with a single assignment and routing policy.
With this approach you get a lot more control over how you want to handle each step, because most of the run-time behavior of the human task is defined at the task level, allowing you to specify different parameters, expiration policies, notification settings, and task forms for each step in the workflow. In addition, on completion of every step, control is returned to the BPEL process, allowing you to carry out some additional processing before executing the next step in the workflow.
One of the draw backs of this approach is that you need to specify a lot more information (roughly n
times as much, where n
is the number of tasks that you have), and often you may be replicating the same information across multiple task definitions as well as having to specify the handling of outcomes for multiple tasks within your BPEL process.
This not only requires more work upfront, but results in a larger and more complicated BPEL process that is not so intuitive to understand and often harder to maintain.
The other potential issue is that the second task doesn't include the comments, task history, and attachments from the previous task. In our case this is important as we want members of the panel to see any comments made by the oBay administrator before they deferred the task.
BPEL allows us to link tasks within the same BPEL process together; to do this double click on the task in the BPEL process that you wish to link to a preceding task. This will open the BPEL Human Task configuration window; from here select the Advanced tab and you will be presented with a variety of options.
If you select the checkbox Include task history from, then you will be presented with a drop down listing all the preceding Human Tasks defined in the BPEL process as illustrated in the following figure:
By selecting one of these, your task is automatically linked to that task and will inherit its task history, comments, and attachments.
The final choice is whether you wish to use the payload from the previous task or create a new payload. This is decided by selecting the appropriate option.
If we look at the Order Fulfillment process, which is used to complete the sale for items won at auction, it is a prime candidate for Human Workflow as it will need to proceed through the following steps in order to complete the sale:
Buyer specifies shipping details (for example address and method of postage)
Seller confirms shipping cost
Buyer notifies the seller that a payment for the item has been made
Seller confirms receipt of payment
Seller notifies the buyer that the item has been shipped
Buyer confirms receipt of item
You may recall from Chapter 9 (oBay Introduction) that we've decided to build a custom user interface (UI) for oBay's customers. As part of the UI we need to enable users to perform each task required to complete the Order Fulfillment process.
One way to achieve this would be to use the Worklist Portlets and embed them directly within the oBay UI. However, oBay want to make the user experience a lot more seamless, so that users are not even aware that they are interacting with any kind of workflow system.
The Workflow Service provides a set of API's just for this kind of scenario. These are exposed as a set of Web Services with support for SOAP and Java WSIF bindings, as well as an equivalent set of Java API's.
Indeed the Worklist application uses the same API's. However, rather than invoke these API's directly from our oBay UI, we are going to build our own Task Based Business Service which acts as a façade around these underlying services. This will give us the architecture depicted in the following diagram:
As we will be using BPEL to implement our Task Based Business Services it makes sense to us the Java WSIF Web Service API (in the same way that any BPEL process containing a Human Task does).
If you compare this to our architecture outlined in Chapter 9, you will notice that we've decided not to wrap a virtual service layer around the workflow service; there are two key reasons for this.
First, if you look at the service description for the workflow services they already provide a very well-defined abstract definition of the service. Hence if you were to re-design the interface they probably wouldn't look a lot different.
Second, whenever we include a Human Workflow task within our process, JDeveloper automatically generates a lot of code which directly uses these services. Thus if we wanted to put a virtual layer over these services we would need to ensure that all our Human Workflow tasks also went via this layer, which is not a trivial activity.
So the reality is adding in a virtual services layer would gain us very little, but would take a lot of effort and we would lose a lot of the advantages provided by the development environment.
For our OrderFulfillment
process, we are taking the approach of combining all six workflow steps into a single Human Task (the OrderFulfillment.task)
. Now this isn't a perfect fit for some of the reasons we've already touched on, so we will look at how we address each of these issues as we encounter them.
Within our task definition we've defined two possible Outcomes for the task either COMPLETED
or ABORTED
(where for some reason the sale fails to proceed). In addition, in the BPEL Human Task configuration window we have configured the Task Title to be set to the item title, and set the Initiator to be the seller of the item.
A key design consideration is to decide on what parameter(s) we are going to pass to the task, taking in to account that we need to pass in the superset of parameters required by each step in the workflow.
For our task we will have a single parameter of type order
which contains all the data required for our task; the definition for this is shown as follows:
<xsd:element name="order" type="act:tOrder"/> <xsd:complexType name="tOrder"> <xsd:sequence> <xsd:element name="orderId" type="xsd:string"/> <xsd:element name="itemId" type="act:tItemId"/> <xsd:element name="orderDesc" type="xsd:string" /> <xsd:element name="sellerId" type="act:tUserId"/> <xsd:element name="buyerId" type="act:tUserId"/> <xsd:element name="orderDetails" type="act:tOrderDetails" /> <xsd:element name="shipTo" type="act:tShipTo" /> </xsd:sequence> </xsd:complexType> <xsd:complexType name="tOrderDetails"> <xsd:sequence> <xsd:element name="orderDate" type="xsd:date"/> <xsd:element name="status" type="xsd:string"/> <xsd:element name="quantity" type="xsd:int"/> <xsd:element name="itemPrice" type="xsd:decimal" /> <xsd:element name="subTotal" type="xsd:decimal" /> <xsd:element name="shippingPrice" type="xsd:decimal" /> <xsd:element name="total" type="xsd:decimal" /> </xsd:sequence> </xsd:complexType> <xsd:complexType name="tShipTo"> <xsd:sequence> <xsd:element name="shippingName" type="xsd:string"/> <xsd:element name="shippingAddress" type="act:tAddress"/> <xsd:element name="additionalInstructions" type="xsd:string"/> </xsd:sequence> </xsd:complexType>
Before we go any further, it's worth spending a moment to highlight some of the key components of this:
Order Id: Potentially we could have multiple orders per auction (for example, if oBay were to support a Dutch auction format at some point in the future), so every order will need its own unique identifier.
As we have made the decision to have a single Human Task, we have a one-to-one mapping between an order and an OrderFulfillment
human task. We will use the task number as our order number.
Ship To: This contains the details provided of where the item is to be sent to as well as the preferred delivery method. This needs to be specified by the buyer in the first step of the workflow.
Shipping Price: Once the buyer has specified the shipping details, the seller can confirm the cost of shipping. This needs to be added to the subTotal
to calculate the total
amount payable.
Status: This field is updated after every step to track where we are in the order fulfillment process.
The most obvious problem from our requirement is that at each step in the process, we will need to update different fields in the order
parameter, and that some of these fields are calculated.
If we were using the default simple task forms generated by JDeveloper for the worklist application then this poses a problem because you can only specify at the parameter level whether it is read only or editable and this will be the same at every step in the task.
One work around is to customize the generated form, which is definitely possible if not entirely straightforward. However, in our scenario, as we are developing our own custom built user interface rather than the worklist application, this issue is easily solved.
For the OrderFulfillment
task, we have specified six Assignment and Routing Policies, one for each step of the workflow. Each one is of type SingleApprover and is assigned dynamically to either the seller or buyer as appropriate, as illustrated in the following screenshot:
The only other potential issue for us is that we need to share generic notification settings for each step in the workflow; for our purpose this is fine as we just want to send a generic notification to our seller or buyer every time a task is assigned to them to notify them that they now need to perform an action in order to complete the sale.
However, if we wanted to send more specific notifications, then we can do that from the BPEL process itself using the notification service. By default the BPEL process will only receive a callback from the Workflow Service upon completion of the task.
However, if you open up the BPEL Human Task configuration window and select the Advanced tab, you will see a checkbox with the option Allow task and routing customizations in BPEL callbacks.
If you select this, your BPEL process will be modified to also receive callbacks when either a task is assigned, updated, or completed as well as when a sub-task is updated.
It does this by replacing the Receive
activity which receives the completed task callback with a Pick
activity embedded within a While
activity that essentially loops until the task is completed, as illustrated:
As you can see the Pick
activity contains an onMessage
branch for each potential callback; you then just add any additional processing that is required to the appropriate onMessage
branch.
In our case we might add a switch to the Task is assigned branch to check where we are in the workflow and then based on that generate the appropriate notification.
Now that we have defined out Order Fulfillment task, the next step is to implement our task based Business Services that will act upon it. If we look at the type of interactions that the user will have with our Order Fulfillment task, they split into two categories. The first being query-based tasks and the second being tasks which change the state of the workflow task. We will look at the query based tasks first.
By analyzing our requirements we can see that we need to support the following query-based operations:
getSoldItems:
Returns a list of all items sold by the specified seller, and provides details of those items which have an outstanding task assigned to the seller.
getPurchasedItems:
Similar to the previous operation but returns a list of all items bought by the specified buyer.
getOrderDetails:
Returns detailed information about a specific order.
It's worth noting that the first two operations are not just returning the current task list for either the buyer or seller, but rather a complete list of all applicable items, regardless of whether the task is currently allocated to the buyer or seller.
We are going to implement each of these operations as a separate BPEL process. To do so we will make use of the Task Query Service provided by the Workflow Service; this provides a number of methods for querying tasks based on a variety of search criteria including status, keywords, attribute values, and so on.
The WSDL for the Task Query Service is defined in the file TaskQueryService.wsdl
which can be found in the directory:
<SOA_HOME>pelsystemservicesschema
The WSDL itself imports a number of other WSDL's and schemas, some of which in turn import additional schemas, all of which can be located in the same directory. In total we need to include the following files in our BPEL project:
TaskQuery.xsd
TaskQueryService.wsdl
TaskQueryService.xsd
TaskQueryServiceInterface.wsdl
UserMetadata.xsd
WorkflowCommon.xsd
WorkflowTask.xsd
We could import these files one by one into our BPEL processes; however, that would be quite tedious. A quicker approach is to copy them directly into our BPEL project.
An alternative approach would be to deploy these files to the BPEL Server so they can be referenced via a URL as described in Chapter 10.
The following screenshot shows the file structure of a BPEL project within JDeveloper.
If you copy all of the above files into the highlighted bpel
sub-directory, JDeveloper will automatically include them in your project. However, before we can use these files to create a PartnerLink we need to make a couple of minor modifications.
The <types>
section of the TaskQueryServiceInterface.wsdl
file contains four import
statements, the last of which (at the time of writing) contains an error. If we look at the following fragment of XML from this file, we can see that the import statement includes the attribute location
.
<import namespace="http://xmlns.oracle.com/bpel/.../taskQueryService"
location="TaskQueryService.xsd"/>
We need to rename this attribute to schemaLocation
as shown here:
<import namespace="http://xmlns.oracle.com/bpel/.../taskQueryService"
schemaLocation="TaskQueryService.xsd"/>
The other modification we need to make is to update the location
attribute of the soap
address for the Task Query Service defined in TaskQueryService.wsdl
.
<service name="TaskQueryService">
<port name="TaskQueryServicePort"
binding="tns:TaskQueryServiceSOAPBinding">
<soap:address location="http://localhost:8888/integration/Task
QueryService"/>
</port>
</service>
We need to modify location
to point to the actual endpoint of the Task Query Service, so set the value of location to:
http://<hostname>:<port>/integration/services/TaskQueryService/ TaskQueryService
Here hostname
represents the name of the machine on which the SOA Suite is running and port
represents the port number.
Once we have copied these files into our project directory and modified them as described above, we are ready to create a partner link.
From this point on, we follow the standard approach for creating a PartnerLink; namely drag a PartnerLink from the Services section in the Component Palette onto your BPEL process. Click on the icon to Browse WSDL Files From Local File System and browse to and select our modified TaskQueryService.wsdl
.
When prompted to make a local copy of the WSDL file and create a WSDL file that defines PartnerLink types select Yes in both cases.
As with the Worklist application, the task query service will only return details of tasks for which you have access, such as the task is assigned to you or you are the task owner or initiator (see Chapter 6 for details).
For authentication purposes, the authenticate
operation is provided, this takes an element of type credential
which consists of the following parameters:
login:
User ID as defined to the underlying Identity Service.
password:
Corresponding password for the specified user.
identityContext:
The identity service enables you to configure multiple identity repositories, each containing it's own set of users. Each repository is identified by its realm name.
The identityContext
should be set to the name of the realm in which the user is defined. Note: jazn.com is the default realm installed out of the box.
onBehalfOfUser:
An optional element, which allows a user with administrative privileges to create a workflow context on behalf of another user by specifying their user id here.
Upon successful authentication, a workflowContext
is returned which is then used in any subsequent calls to the workflow service.
If you are calling a single workflow service, you can provide the authentication details as part of that service invocation, instead of a separate call to the authentication service. This removes the overhead of having to make two calls to the query service.
When creating the credential
element, we need to ensure that it doesn't include an empty onBehalfOfUser
element. The service will try to create a workflow context for this empty
user, which of course will fail and return an error.
This is an easy error to make, because the first time we use an assign statement to populate any sub-element of credential, for example doing a copy to populate the login
element, BPEL PM, by default, will create an initialized credential
element containing all its sub-elements including onBehalfOfUser
(each with an empty value).
A simple way round this is to assign a fragment of xml, such as the following:
<credential xmlns="http://xmlns.oracle.com/bpel/workflow/common"> <login/> <password/> <identityContext>jazn.com</identityContext> </credential>
Directly to credential
, this acts as a template into which we can copy the required values for login
and password
. We do this using a copy operation within an assign statement, the key difference being that we specify an XML Fragment as the From Type as shown in the following screenshot:
Note, we have specified the default namespace in the credential
element so that all elements are created in the appropriate namespace.
The queryTask
operation returns a list of tasks for a user, which you can filter based on criteria similar to that provided by the Worklist application. The following diagram shows the structure of the input it expects.
We can see the taskListRequest
consists of two elements: the workflowContext
, which should contain the value returned by our authentication request and other is the taskPredicateQuery
, which defines the actual query that we wish to make.
The taskPredicateQuery
consists of the following core elements:
displayColumnList:
Allows us to specify which attributes of the task (for example, title, created by, created date, etc) we want to be included in the result set.
optionalInfoList:
Allows us to specify any additional information we want returned with each task, such as comments, task history, etc.
predicate:
Used to specify the filter conditions for which tasks we want returned.
ordering:
Allows us to specify one or more columns on which we want to sort the result set.
The two attributes startRow
and endRow
are used to control whether the entire result set is returned by the query or just a subset. To return the entire result set, set both attributes to zero. To only return a subset of the result set then set the attributes appropriately. For example to return the first ten tasks in the result set, you would set the startRow
equal to 1 and the endRow
equal to 10.
The displayColumnList
element list contained within the taskPredicateQuery
allows us to define which task attributes (or columns) we want returned by our query.
Simply include in here one displayColumn
entry per task attributes we want returned, valid values include TaskNumber, Title, Priority, Creator, CreatedDate
and State
.
For a full list of task attributes
see the list of WorklistColumns defined in the Constant Field Values section of the Workflow Services Java API Reference that comes with the Oracle BPEL Process Manager documentation.
These tend to match the column names in the WFTASK table in the BPEL PM Database schema.
If we look at the WSDL definition for the getSoldItems
operation we can see that it returns the values orderNo, itemId, orderDesc, buyerId, itemPrice, totalPrice, saleDate, orderStatus, lastUpdateDate
, and nextAction
.
At first glance only a couple of these match to actual task attributes; when we created the task we set the task title
to hold orderDesc
and the task attribute updatedDate
maps to lastUpdateDate
.
In addition, we have decided to use taskNumber
for the orderNo
as this makes it a lot simpler to tie the two together.
However, the remaining fields are all held in the task payload which we can't access through the queryTask
operation. One solution would be to call the getTaskDetails
operation for every row returned, but this would hardly be efficient. Fortunately we have an alternative approach and that is to use Flex Fields.
Flex fields are a set of generic attributes attached to a task which can be populated with information from the task payload. This information can be displayed in the task listing as well as used for querying (and defining workflow rules in the worklist application).
The simplest way to initialize the Flex fields is in the BPEL process which creates the task. If you click on the plus sign next to a Human Task activity, this will expand the task showing you the individual BPEL activities that are used to invoke it, as illustrated in the following figure:
You will see that this starts with a number of assign activities, the second one of which (circled) is used to set the system attributes. To set the Flex fields simply open the assign activity, and add an extra copy statement for each Flex field required.
For our purposes we will set the following Flex fields in our Order Fulfillment task.
Flex field |
Attribute |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You will need to update the local variable
initiateTaskInput
which will be defined in the scope with the same name as the Human Task ( OrderFulfillment
in our case). The Flex fields are located in the systemMessageAttributes
element of the task
element as illustrated in the following screenshot:
Once we have populated the Flex fields we can access them in our query just like any other task attribute. This will give us a displayColumnList
that looks as follows:
<displayColumnList xmlns="http://xmlns.oracle.com/bpel/workflow/taskQuery"> <displayColumn> TaskNumber</displayColumn> <displayColumn> Title</displayColumn> <displayColumn> UpdatedDate</displayColumn> <displayColumn> TextAttribute1</displayColumn> <displayColumn> TextAttribute2</displayColumn> <displayColumn> NumberAttribute1</displayColumn> <displayColumn> NumberAttribute2</displayColumn> <displayColumn> DateAttribute1</displayColumn> <displayColumn> TextAttribute3</displayColumn> <displayColumn> TextAttribute4</displayColumn> </displayColumnList>
The next step is to specify the query predicate so that it only returns those tasks that we are interested in. We will first look at the query we need to construct to return all sold items for a particular seller.
The following diagram shows the structure of the query predicate. The assignmentFilter
allows us to specify a filter based on who the task is currently assigned to. Valid values are All, My, Group, My+Group, Reportees, Creator, Owner, Previous
, or Admin
.
For our purpose we need to list all tasks related to items sold by the specified seller; so we will need to include those items which have tasks currently assigned to the buyer.
You may recall that when we defined our workflow we assigned the initiator (or creator) of the task to be the seller, so we can use Creator
as the assignmentFilter
.
So far our query will return all tasks created by the specified user, which could potentially include tasks created in other workflows, so we need to add an additional filter to further restrict our query.
One approach would be to use the keywords
filter. This is an optional search string, which, if specified, will only return tasks where the string is contained in the task title, task identification key, or one of the task text Flex fields. However, this probably won't result in the most efficient query, so we should use an alternative if available.
For this we need to use the clause
element (shown in the figure above). This allows us to specify one of more clauses against any of the task attributes.
One such attribute is the TaskDefinitionName
, which contains the name that we assigned to our task when we defined it in JDeveloper (that is, OrderFulfillment)
. Adding a clause to filter on this would give us the following predicate:
<predicate xmlns="http://xmlns.oracle.com/bpel/workflow/taskQuery"> <assignmentFilter> Creator</assignmentFilter> <clause joinOperator="AND"> <column> TaskDefinitionName</column> <operator> EQ</operator> <value> OrderFulfillment</value> </clause> </predicate>
Specifying the query predicate for the buyer isn't quite so simple as we want to list all tasks related to items bought by the specified buyer, so we will need to include those items which have tasks currently assigned to various sellers.
Unlike the seller's query, we can't use the Creator
value as our assignment filter, and we can't use My
either as this only return tasks currently assigned to us. Hence, the only option we have is to use All
as our assignment filter. However, this will return all tasks currently in the system, so we need to find a way of restricting the list to just those tasks required by the buyer.
As you may recall we have already defined the Flex field textAttribute1
to hold the buyerId
, so we just need to add an extra clause to our predicate to test for this condition. This will give us a predicate that looks as follows:
<predicate xmlns="http://xmlns.oracle.com/bpel/workflow/taskQuery"> <assignmentFilter> All</assignmentFilter> <clause joinOperator="AND"> <column> TextAttribute1</column> <operator> EQ</operator> <value> $buyerId</value> </clause> <clause joinOperator="AND"> <column> TaskDefinitionName</column> <operator> EQ</operator> <value> OrderFulfillment</value> </clause> </predicate>
where $buyerId
needs to be substituted with the actual userId
of the buyer.
The ordering
element list contained within the taskPredicateQuery
allows us to define which task attributes we want to order our result set by. For our purpose we want to order by sale date, which is held in dateAttribute1
; this gives us an ordering
element which looks as follows:
<ordering xmlns="xmlns.oracle.com/bpel/workflow/taskQuery"> <clause> <column> DateAttribute1</column> <sortOrder> ASCENDING</sortOrder> </clause> </ordering>
The simplest way to create the taskPredicateQuery
is to create an XML Fragment which can act as a template for the query and assign this with a single copy
statement. Then just add any additional copy statements for those values which need to be specified at run time in order to modify the template generated value appropriately.
The final query based operation we need to implement is getOrderDetails
, which returns the order details for the specified orderNo
. The Task Query Service provides two similar operations getTaskDetailsByNumber
and getTaskDetailsById
.
As the orderNo
corresponds to the taskNumber
, it makes sense to call the getTaskDetailsByNumber
operation. This just takes the standard workflowContext
and the taskNumber
as its input.
The only slight area of complexity is extracting the order from the task payload. This is because payload
is defined as xsd:any
, which means it can contain any value. Because of this the XPath mapping tool can't determine the structure of the payload and thus can't visually map the From part of the operation.
Thus you have to create the XPath manually. The simplest way to do this is to create a mapping from the task to your target variable using the visual editor and then modify the XPath manually as shown in the following screenshot:
Our second category of Task Based Business Service is the one that allows the buyer or seller to perform actions against the workflow task. For the purpose of this section we will look at the implementation of the setShippingDetails
operation, though the other operations submitInvoice, notifyPaymentMade, confirmPaymentReceived, notifyItemShipped
, and confirmItemReceived
all follow the same basic pattern.
setShippingDetails
is used to complete the first step in the workflow, namely update the task payload to contain the shipping name and address of the buyer as well as provide any additional shipping instructions. Finally it needs to set the outcome
of the current step to COMPLETED
so that the task moves on to the next step in the workflow. The following diagram shows the input fields for this operation.
From this we can see it contains the buyer's credentials
, required to authenticate with the Workflow Services, the orderNo
which we will use to locate the appropriate order fulfillment task, and the actual shipTo
details which we will use to update the task.
To implement this operation we are going to make use of the Task Service provided by the Workflow Service; this provides a number of operations which act on a task.
The WSDL for the Task Service is defined in the file TaskServiceWSIF.wsdl
which can be found in the directory:
<SOA_HOME>pelsystemservicesschema
The WSDL itself imports a number of other WSDLs and schemas, some of which in turn import additional schemas, all of which can be located in the same directory. In total we need to include the following files in our BPEL project:
RoutingSlip.xsd
TaskService.xsd
TaskServiceInterface.wsdl
TaskServiceWSIF.wsdl
WorkflowCommon.xsd
WorkflowTask.xsd
To include these files within our project, we can copy them into the bpel
sub-directory in the same way we described earlier for the Task Query Service and create a corresponding PartnerLink.
An alternative approach is to add a Human Task activity to our BPEL process. In doing so, JDeveloper will automatically add all the required resources to our project. This is because BPEL makes use of the same Task Service to create a workflow task in the first place. We can then remove the actual Human Task activity from our process.
Most of the tasks provided by this service are granular in nature and only update a specific part of a task. Thus they only require the taskId
and the corresponding part of the task being updated as input.
However, our operation needs to update multiple parts of a task, that is, the order held in the task payload, the corresponding Flex fields and the task outcome. For this we will use the updateTask
operation. The following diagram shows its expected input:
From this we can see that it expects the standard workflowContext
as well as the complete updated task
element.
The simplest way to achieve this is to use the Task Query Service to get an up-to-date copy of our task
. We do this in exactly the same way we did for our getOrderDetails
operation, but then modifying it as appropriate and calling the updateTask
operation to make the changes.
The only area of complexity is updating the order
directly within the task payload. This is for the same reason we mentioned earlier when implementing the getOrderDetails
operation; as the payload is defined as xsd:any
, we can't use the XPath mapping tool to visually map the updates.
The simplest way to work around this is to first extract the order
from the task payload into a local variable (which we do in exactly the same way that we did for our getOrderDetails
operation).
Once we've done this we can update the shipTo
element of the order
to hold the shipping details as well as update its status to Awaiting Shipping Costs to reflect the next step in the workflow.
Once we have updated the order
, we must insert it into the task payload; this is essentially the reverse of the copy operation we used to extract it.
Once we have updated the task payload we then need to update the corresponding Flex fields so that they remain synchronized with the order. We do this using an assign
activity in a similar way that we used to set the Flex fields when creating the task in our OrderFulfillment
process.
Finally, we need to set the task outcome for the current step (this is effectively the same as specifying a task action through the worklist application). In our case we have defined two potential outcomes: COMPLETED
or ABORTED
.
For setShippingDetails
(as with all of our operations), we want to set the task outcome to COMPLETED
. Note that this won't actually complete the task, rather it completes the current assignment, and in our case since all our routing policies are single approver, it will complete the current step in the workflow and move the task on to the next step. Only once the final step is completed will the task complete and control be returned to the OrderFulfillment
BPEL process.
To set the task outcome, we only need to set the outcome
element (located in the task systemAttributes
element) to COMPLETED
. However, it isn't quite that straightforward; if you look at the actual task data returned by the getTaskDetailsByNumber
operation, the outcome
element isn't present.
Thus, if we use a standard copy operation to try to assign a value to this element we will get an XPath exception.
Instead what we need to do is create the outcome
element and its associated value and append it to the systemAttributes
element. To do this within the assign activity, use an Append Operation as shown in the following diagram:
The simplest way to create the outcome
element is to use an XML Fragment and append it to the systemAttributes
element as shown in the following screenshot:
Once we've done this we will have a completed task, so that all that remains to do is call updateTask
to complete the operation.
Human workflow is a key requirement for many projects. Quite often these are a lot more demanding than just a simple approval. In this chapter, we've looked at some of the more complex, yet common use cases and shown how these can be addressed in a relatively straightforward fashion by the Workflow Service.
In addition, we've demonstrated how we can use the Workflow API to completely abstract out the underlying Workflow Service and present a completely different appearance to the consumer of the service.
While we have not covered every detail of the Workflow Service, you should now have a good appreciation of some of its more advanced features, the versatility this gives you and more importantly how you can apply them to solve some of the more common workflow requirements.
3.14.246.148