Chapter 15. Workflow Patterns

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.

Managing multiple participants in a workflow

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.

Using multiple assignment and routing policies

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>

Determining the outcome by a group vote

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:

Determining the outcome by a group vote

Assigning participants

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.

Skipping the second step

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.

Sharing attachments and comments

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.

Deciding on the outcome

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.

Using multiple Human Tasks

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.

Linking individual Human Tasks

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.

Linking individual Human Tasks

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.

Using the workflow API

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:

Using the workflow API

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).

Note

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.

Defining the order fulfillment Human Task

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.

Specifying task parameters

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.

Specifying the routing policy

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:

Specifying the routing policy

Notification settings

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:

Notification settings

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.

Querying task instances

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.

Defining a Partner Link for the Task Query Service

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.

Note

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.

Defining a Partner Link for the Task Query Service

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.

Modifying TaskQueryServiceInterface.wsdl

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"/>

Modifying TaskQueryService.wsdl

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.

Note

It's not just a case of modifying the hostname and port; we also need to modify the remainder of the URL.

Creating the Task Query Service PartnerLink

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.

User authentication

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.

Note

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.

Create credential element

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:

Create credential element

Note, we have specified the default namespace in the credential element so that all elements are created in the appropriate namespace.

Querying tasks

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.

Querying tasks

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.

Specifying the Display Column List

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.

Note

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

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).

Populating Flex Fields

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:

Populating Flex Fields

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

textAttribute1

itemId

textAttribute2

buyerId

numberAttribute1

salePrice

numberAttribute2

totalPrice

dateAttribute1

saleDate

textAttribute3

orderStatus

textAttribute4

nextAction

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:

Populating Flex Fields

Accessing Flex fields

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>

Specifying the query predicate

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.

Specifying the query predicate

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>

Using Flex fields in the query 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.

Ordering the data

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>

Note

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.

Getting task details

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:

Getting task detailstaskPredicateQuery creating

Updating a task instance

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.

Updating a task instance

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.

Defining a PartnerLink for the Task Service

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.

Using the updateTask operation

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:

Using the updateTask operation

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.

Updating the task payload

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.

Updating the task Flex fields

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.

Updating the task outcome

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:

Updating the task outcome

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:

Updating the task outcome

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.157.6