Red Hat Training

A Red Hat training course is available for JBoss Enterprise SOA Platform

Chapter 8. Task Management

The jBPM's core role is to persist the execution of a process. This feature is extremely useful when one is seeking to manage tasks and task-lists for people. The jBPM allows one to specify a piece of software that describes an overall process. Such a piece of software can have wait states for human tasks.

8.1.  Tasks

Tasks are part of the process definition. They define how task instances will be created and assigned during process executions.
Define tasks in task-nodes and in the process-definition. The most common way is to define one or more tasks in a task-node. In that case the task-node represents a task to be undertaken by the user and the process execution should wait until the actor completes the task. When the actor completes the task, process execution continues. When more tasks are specified in a task-node, the default behaviour is to wait until all the tasks have ended.
One can also specify tasks on the process-definition. Tasks specified in this way can be found by searching for their names. One can also reference them from within task-nodes or use them from within actions. In fact, every task (or task-node) that is given a name can be found in the process-definition.
Ensure that each task name is unique. Also, give the task a priority. This will be used as the initial priority for each task instance created for this task. (This initial priority can be changed by the task instance afterwards.)

8.2.  Task Instances

It is possible to assign a task instance to an actorId (java.lang.String). Every task instance is stored in one table (JBPM_TASKINSTANCE.) Query this table for every task instances for a given actorId, in order to obtain the task list for that particular user.
Use the jBPM task list mechanism to combine jBPM tasks with other tasks, even when those other tasks are unrelated to a process execution. In this way, one can easily combine jBPM-process-tasks with other application's tasks in one centralised repository.

8.2.1.  Task Instance Life-Cycle

The task instance life-cycle is straightforward: after creation, one can start the instances. They can then be ended, which means that they will be marked as completed.

Note

For the sake of flexibility, assignment is not part of the life-cycle.
  1. Task instances are normally created when the process execution enters a task-node (via the TaskMgmtInstance.createTaskInstance(...) method.)
  2. A user interface component then queries the database for the task lists. It does so by using the TaskMgmtSession.findTaskInstancesByActorId(...) method.
  3. Then, after collecting input from the user, the UI component calls TaskInstance.assign(String), TaskInstance.start() or TaskInstance.end(...).
A task instance maintains its state by means of three date-properties:
  1. create
  2. start
  3. end
Access these properties via their respective "getters", which can be found on the TaskInstance.
Completed task instances are marked with an end date so that they are not fetched when subsequent queries search for tasks lists. The completed tasks do, however, remain in the JBPM_TASKINSTANCE table.

8.2.2.  Task Instances and Graph Executions

Task instances are the items in an actor's task list. A signalling task instance is a task instance that, when completed, sends a signal to its token to continue the process execution. Blocking task instances are those that the related token (the path of execution) is not allowed to leave the task-node before the task instance is completed. By default, task instances are configured to be signalling and non-blocking.
If more than one task instance is associated with a task-node, the process developer can specify the way in which completion of the task instances affects continuation of the process. Give any of these values to the task-node's signal-property:
last
This is the default. It proceeds execution when the last task instance has been completed. When no tasks are created on entrance of this node, execution is continued.
last-wait
This proceeds execution when the last task instance has been completed. When no tasks are created on entrance of this node, execution waits in the task node until tasks are created.
first
This proceeds execution when the first task instance has been completed. When no tasks are created upon the entry of this node, execution is continued.
first-wait
This proceeds execution when the first task instance has been completed. When no tasks are created on entrance of this node, execution waits in the task node until tasks are created.
unsynchronized
In this case, execution always continues, regardless of whether tasks are created or still unfinished.
never
In this case, execution never continues, regardless whether tasks are created or still unfinished.
Task instance creation can be based upon a run-time calculation. In these cases, add an ActionHandler to the task-node's node-enter event and set create-tasks="false". Here is an example:
public class CreateTasks implements ActionHandler {
  public void execute(ExecutionContext executionContext) throws Exception {
    Token token = executionContext.getToken();
    TaskMgmtInstance tmi = executionContext.getTaskMgmtInstance();
      
    TaskNode taskNode = (TaskNode) executionContext.getNode();
    Task changeNappy = taskNode.getTask("change nappy");

    // now, 2 task instances are created for the same task.
    tmi.createTaskInstance(changeNappy, token);
    tmi.createTaskInstance(changeNappy, token);
  }
}
Here, the tasks to be created are specified in the task-node. They could also be specified in the process-definition and fetched from the TaskMgmtDefinition. (TaskMgmtDefinition extends the process definition by adding task management information.)
The TaskInstance.end() method is used to mark task instances as completed. One can optionally specify a transition in the end method. In case the completion of this task instance triggers continuation of the execution, the task-node is left over the specified transition.

8.3.  Assignment

A process definition contains task nodes. A task-node contains zero or more tasks. Tasks are static descriptions of part of the process definition. At run-time, executing tasks result in the creation of task instances. A task instance corresponds to one entry in a person's task list.
With the jBPM, one can apply the push (personal task list) and pull (group task list) models of task assignment in combination. The process determines those responsible for a task and push it to their task lists. A task can also be assigned to a pool of actors, in which case each of the actors pull the task and put it in their personal task lists.

8.3.1.  Assignment Interfaces

Assign task instances via the AssignmentHandler interface:
public interface AssignmentHandler extends Serializable {
  void assign( Assignable assignable, ExecutionContext executionContext );
}
An assignment handler implementation is called when a task instance is created. At that time, the task instance is assigned to one or more actors. The AssignmentHandler implementation calls the assignable methods (setActorId or setPooledActors) to assign a task. The assignable item is either a TaskInstance or a SwimlaneInstance (that is, a process role).
public interface Assignable {
  public void setActorId(String actorId);
  public void setPooledActors(String[] pooledActors);
}
Both TaskInstances and SwimlaneInstances can be assigned to a specific user or to a pool of actors. To assign a TaskInstance to a user, call Assignable.setActorId(String actorId). To assign a TaskInstance to a pool of candidate actors, call Assignable.setPooledActors(String[] actorIds).
One can associate each task in the process definition with an handler implementation to perform the assignment at run-time.
When more than one task in a process should be assigned to the same person or group of actors, consider the usage of a swimlane, see Section 8.6, “ Swimlanes ”.
To create reusable AssignmentHandlers, configure each one via the processdefinition.xml file. (See Section 14.2, “Delegation” for more information on how to add configuration to assignment handlers.)

8.3.2.  The Assignment Data Model

The data model for managing assignments of task instances and swimlane instances to actors is the following. Each TaskInstance has an actorId and a set of pooled actors.
The assignment model class diagram

Figure 8.1. The assignment model class diagram

The actorId is the responsible for the task, while the set of pooled actors represents a collection of candidates one of whom will become responsible if they take the task. Both actorId and pooledActors are optional and can also be combined.

8.3.3.  The Personal Task List

The personal task list denotes all the task instances that are assigned to a specific individual. This is indicated by the presence of the actorId property on a TaskInstance. Put a TaskInstance in someone's task list in one of these ways:
  • specify an expression in the task element's actor-id attribute
  • use the TaskInstance.setActorId(String) method from anywhere in the code
  • use the assignable.setActorId(String) in an AssignmentHandler
To fetch the personal task list for a given user, use TaskMgmtSession.findTaskInstances(String actorId).

8.3.4.  The Group Task List

The pooled actors are the group of candidates to whom the task is offered. One candidate has to accept it. Users can not start working on tasks immediately as that would, potentially, result in a conflict if many people commenced work on the same task. To prevent this, users can only take task instances from the group task list and move these into their personal task lists. It is only when a task is placed on the user's personal task list that her or she can commence working on it.
To put a taskInstance in someone's group task list, add the user's actorId or one of the user's groupIds to the pooledActorIds. To specify the pooled actors, use one of the following methods:
  • specify an expression in the attribute pooled-actor-ids of the task element in the process
  • use TaskInstance.setPooledActorIds(String[]) from anywhere in your code
  • use assignable.setPooledActorIds(String[]) in an AssignmentHandler
To fetch the group task list for a given user, make a collection that includes the user's actorId and those of all the groups to which the user belongs. Use TaskMgmtSession.findPooledTaskInstances(String actorId) or TaskMgmtSession.findPooledTaskInstances(List actorIds) to search for task instances that are not in a personal task list (actorId==null) and for which there is a match amongst the pooled actorId.

Note

The software was designed this way in order to separate the identity component from jBPM task assignment. The jBPM only stores strings as actorIds. It does not understand the relationships between the users and groups or any other identity information.
The actorId always overrides pooled actors. Hence, a taskInstance that has an actorId and a list of pooledActorIds will only show up in the actor's personal task list. Retain the pooledActorIds in order to put a task instance back into the group by simply setting the taskInstance's actorId property to null.

8.4.  Task Instance Variables

A task instance can have its own set of variables and can also "see" the process variables. Task instances are usually created in an execution path (a token). This creates a parent-child relation between the token and the task instance, which is similar to the parent-child relation between the tokens themselves. Note that the normal scoping rules apply.
Use the controller to create, populate and submit variables between the task instance scope and the process scoped variables.
This means that a task instance can 'see' its own variables plus all the variables of its related token.
The controller can be used to create populate and submit variables between the task instance scope and the process scoped variables.

8.5.  Task Controllers

When task instances are created, one can use task controllers populate the task instance variables. When the task instances terminate, one can use task controllers to submit the data belonging to them to the process variables.

Note

Use of task controllers is optional. Task instances also are able to "see" the process variables related to its token. Use task controllers to undertake these tasks:
  • create copies of task instance variables so that intermediate updates to them do not affect the process variables until the process is finished. At this time, the copies are submitted back into the process variables.
  • the task instance variables do not have a one-to-one relationship with the process variables. For instance, if the process has variables named sales in January sales in February and sales in March, then the task instance form might need to show the average sales for those three months.
Tasks collect input from users. But there are many user interfaces which could be used to present the tasks to the users. E.g. a web application, a swing application, an instant messenger, an email form,... So the task controllers make the bridge between the process variables (=process context) and the user interface application. The task controllers provide a view of process variables to the user interface application.
When a task instance is created, the task controller translates process variables, if there are any, into task variables. The task variables serve as the input for the user interface form. The user input itself is stored in the task variables. When the user ends the task, the task controller updates the process variables based on the task instance data.
The task controllers

Figure 8.2. The task controllers

In a simple scenario, there is a one-on-one mapping between process variables and the form parameters. Specify task controllers in a task element. In this case, the default JBPM task controller can be used. It takes a list of variable elementswhich express how the process variables are copied in the task variables.
The next example demonstrates how to create separate copies of task instance variable, based on the process variables:
<task name="clean ceiling">
  <controller>
    <variable name="a" access="read" mapped-name="x" />
    <variable name="b" access="read,write,required" mapped-name="y" />
    <variable name="c" access="read,write" />
  </controller>
</task>
The name attribute refers to the name of the process variable. The mapped-name is optional and refers to the name of the task instance variable. If the mapped-name attribute is omitted, mapped-name defaults to the name. Note that the mapped-name is also used as the label for the fields in the web application's task instance form.
Use the access attribute to specify as to whether or not the variable copied at task instance creation, will be written back to the process variables at task instance conclusion. (This information can be used by the user interface to generate the proper form controls.) The access attribute is optional and the default access is read,write.
A task-node can have many tasks whilst a start-state has one task.
If the simple one-to-one mapping between process variables and form parameters is too limiting, create a custom TaskControllerHandler implementation. Here is the interface for it:.
public interface TaskControllerHandler extends Serializable {
  void initializeTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
  void submitTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
}
This code sample demonstrates how to configure it:
<task name="clean ceiling">
  <controller class="com.yourcom.CleanCeilingTaskControllerHandler">
    -- here goes your task controller handler configuration --
  </controller>
</task>

8.6.  Swimlanes

A swimlane is a process role. Use this mechanism to specify that multiple tasks in the process are to be undertaken by the same actor. After the first task instance for a given swimlane is created, the actor is "remembered" for every subsequent task in the same swimlane. A swimlane therefore has one assignment. Study Section 8.3, “ Assignment ” to learn more.
When the first task in a given swimlane is created, the AssignmentHandler is called. The Assignable item that is passed to the AssignmentHandler is SwimlaneInstance. Every assignment undertaken on the task instances in a given swimlane will propagate to the swimlane instance. This is the default behaviour because the person that takes a task will have a knowledge of that particular process. Hence, ever subsequent task instances in that swimlane is automatically assigned to that user.

8.7.  Swimlane in Start Task

It is possible to associate a swimlane with the start task. One does this to capture the process initiator.
A task can be specified in a start-state, which will associate it with a swimlane. When a new task instance is created, the current authenticated actor is captured via the Authentication.getAuthenticatedActorId() method. The actor is stored in the start task's swimlane.
<process-definition>
  <swimlane name='initiator' />
  <start-state>
    <task swimlane='initiator' />
    <transition to='...' />
  </start-state>
  ...
</process-definition>
Add variables to the start task using the normal method. Do so to define the form associated with the task. See Section 8.5, “ Task Controllers ” for more information.

8.8.  Task Events

One can associate actions with tasks. There are four standard event types:
  1. task-create, which is fired when a task instance is created.
  2. task-assign, which is fired when a task instance is being assigned. Note that in actions that are executed on this event, one can access the previous actor with the executionContext.getTaskInstance().getPreviousActorId(); method.
  3. task-start, which is fired when the TaskInstance.start() method is called. Use this optional feature to indicate that the user is actually starting work on the task instance.
  4. task-end, which is fired when TaskInstance.end(...) is called. This marks the completion of the task. If the task is related to a process execution, this call might trigger the resumption of the process execution.

Note

Exception handlers can be associated with tasks, For more information about this, read Section 6.7, “ Exception Handling ”.

8.9.  Task Timers

One can specify timers on tasks. See Section 9.1, “ Timers ”.
It is possible to customise cancel-event for task timers. By default, a task timer cancels when the task is ended but with the cancel-event attribute on the timer, one can customise that to task-assign or task-start. The cancel-event supports multiple events. To combine cancel-event types, specify them in a comma-separated list in the attribute.

8.10.  Customizing Task Instances

To customise a task instance, follow these steps:
  1. create a sub-class of TaskInstance
  2. create a org.jbpm.taskmgmt.TaskInstanceFactory implementation
  3. configure the implementation by setting the jbpm.task.instance.factory configuration property to the fully qualified class name in the jbpm.cfg.xml file.
  4. if using a sub-class of TaskInstance, create a Hibernate mapping file for the sub-class (using extends="org.jbpm.taskmgmt.exe.TaskInstance"
  5. add that mapping file to the list in hibernate.cfg.xml.

8.11.  The Identity Component

Management of users, groups and permissions is termed identity management. The jBPM includes an optional identity component. One can easily replace it with one's company's own data store.
The jBPM identity management component holds knowledge of the organisational model and uses this to assign tasks. This model describes the users, groups, systems and the relationships between these. Optionally, permissions and roles can also be included.
The jBPM handles this by defining an actor as an actual participant in a process. An actor is identified by its ID called an actorId. The jBPM has only knowledge about actorIds and they are represented as java.lang.Strings for maximum flexibility. So any knowledge about the organizational model and the structure of that data is outside the scope of the jBPM's core engine.
As an extension to jBPM we will provide (in the future) a component to manage that simple user-roles model. This many to many relation between users and roles is the same model as is defined in the J2EE and the servlet specs and it could serve as a starting point in new developments.
Note that the user-roles model as it is used in the servlet, ejb and portlet specifications, is not sufficiently powerful for handling task assignments. That model is a many-to-many relation between users and roles. This doesn't include information about the teams and the organizational structure of users involved in a process.

8.11.1. The identity model

The identity model class diagram

Figure 8.3. The identity model class diagram

The classes in yellow are those which pertain to the expression assignment handler discussed next.
A User represents a user or a service. A Group is any kind of group of users. Groups can be nested to model the relation between a team, a business unit and the whole company. Groups have a type to differentiate between the hierarchical groups and e.g. hair color groups. Memberships represent the many-to-many relation between users and groups. A membership can be used to represent a position in a company. The name of the membership can be used to indicate the role that the user fulfills in the group.

8.11.2. Assignment expressions

The identity component comes with one implementation that evaluates an expression for the calculation of actors during assignment of tasks. Here's an example of using the assignment expression in a process definition:
<process-definition>
  <task-node name='a'>
    <task name='laundry'>
      <assignment expression='previous --> group(hierarchy) --> member(boss)' />
    </task>
    <transition to='b' />
  </task-node>
      
      <para>Syntax of the assignment expression is like this:</para>
      first-term --> next-term --> next-term --> ... --> next-term

where

first-term ::= previous |
               swimlane(swimlane-name) |
               variable(variable-name) |
               user(user-name) |
               group(group-name)

and 

next-term ::= group(group-type) |
              member(role-name)
</programlisting>

8.11.2.1. First terms

An expression is resolved from left to right. The first-term specifies a User or Group in the identity model. Subsequent terms calculate the next term from the intermediate user or group.
previous means the task is assigned to the current authenticated actor. This means the actor that performed the previous step in the process.
swimlane(swimlane-name) means the user or group is taken from the specified swimlane instance.
variable(variable-name) means the user or group is taken from the specified variable instance. The variable instance can contain a java.lang.String, in which case that user or group is fetched from the identity component. Or the variable instance contains a User or Group object.
user(user-name) means the given user is taken from the identity component.
group(group-name) means the given group is taken from the identity component.

8.11.2.2. Next terms

group(group-type) gets the group for a user. Meaning that previous terms must have resulted in a User. It searches for the the group with the given group-type in all the memberships for the user.
member(role-name) gets the user that performs a given role for a group. The previous terms must have resulted in a Group. This term searches for the user with a membership to the group for which the name of the membership matches the given role-name.

8.11.3. Removing the identity component

When you want to use your own datasource for organizational information such as your company's user database or LDAP system, you can remove the jBPM identity component. The only thing you need to do is make sure that you delete the following lines from the hibernate.cfg.xml.
<mapping resource="org/jbpm/identity/User.hbm.xml"/>
<mapping resource="org/jbpm/identity/Group.hbm.xml"/>
<mapping resource="org/jbpm/identity/Membership.hbm.xml"/>
The ExpressionAssignmentHandler is dependent on the identity component so you will not be able to use it as is. In case you want to reuse the ExpressionAssignmentHandler and bind it to your user data store, you can extend from the ExpressionAssignmentHandler and override the method getExpressionSession.
protected ExpressionSession getExpressionSession(AssignmentContext assignmentContext);