Chapter 11. Task management

The core business of jBPM is the ability to persist the execution of a process. A situation in which this feature is extremely useful is the management of tasks and tasklists for people. jBPM allows to specify a piece of software describing an overall process which can have wait states for human tasks.

11.1. Tasks

Tasks are part of the process definition and they define how task instances must be created and assigned during process executions.

Tasks can be defined 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 done by the user and the process execution should wait until the actor completes the task. When the actor completes the task, process execution should continue. When more tasks are specified in a task-node, the default behaviour is to wait for all the tasks to complete.

Tasks can also be specified on the process-definition. Tasks specified on the process definition can be looked up by name and referenced from within task-nodes or used from inside actions. In fact, all tasks (also in task-nodes) that are given a name can be looked up by name in the process-definition.

Task names must be unique in the whole process definition. Tasks can be given a priority. This priority will be used as the initial priority for each task instance that is created for this task. TaskInstances can change this initial priority afterwards.

11.2. Task instances

A task instance can be assigned to an actorId (java.lang.String). All task instances are stored in one table of the database (JBPM_TASKINSTANCE). By querying this table for all task instances for a given actorId, you get the task list for that perticular user.

The jBPM task list mechanism can combine jBPM tasks with other tasks, even when those tasks are unrelated to a process execution. That way jBPM developers can easily combine jBPM-process-tasks with tasks of other applications in one centralized task-list-repository.

11.2.1. Task instance lifecycle

The task instance lifecycle is straightforward: After creation, task instances can optionally be started. Then, task instances can be ended, which means that the task instance is marked as completed.

Note that for flexibility, assignment is not part of the life cycle. So task instances can be assigned or not assigned. Task instance assignment does not have an influence on the task instance life cycle.

Task instances are typically created by the process execution entering a task-node (with the method TaskMgmtInstance.createTaskInstance(...)). Then, a user interface component will query the database for the tasklists using the TaskMgmtSession.findTaskInstancesByActorId(...). Then, after collecting input from the user, the UI component calls TaskInstance.assign(String), TaskInstance.start() or TaskInstance.end(...).

A task instance maintains it's state by means of date-properties : create, start and end. Those properties can be accessed by their respective getters on the TaskInstance.

Currently, completed task instances are marked with an end date so that they are not fetched with subsequent queries for tasks lists. But they remain in the JBPM_TASKINSTANCE table.

11.2.2. Task instances and graph execution

Task instances are the items in an actor's tasklist. Task instances can be signalling. A signalling task instance is a task instance that, when completed, can send a signal to its token to continue the process execution. Task instances can be blocking, meaning that the related token (=path of execution) is not allowed to leave the task-node before the task instance is completed. By default task instances are signalling and non-blocking.

In case more than one task instance are associated with a task-node, the process developer can specify how completion of the task instances affects continuation of the process. Following is the list of values that can be given to the signal-property of a task-node.

  • last: This is the default. Proceeds execution when the last task instance is completed. When no tasks are created on entrance of this node, execution is continued.
  • last-wait: Proceeds execution when the last task instance is completed. When no tasks are created on entrance of this node, execution waits in the task node till tasks are created.
  • first: Proceeds execution when the first task instance is completed. When no tasks are created on entrance of this node, execution is continued.
  • first-wait: Proceeds execution when the first task instance is completed. When no tasks are created on entrance of this node, execution waits in the task node till tasks are created.
  • unsynchronized: Execution always continues, regardless wether tasks are created or still unfinished.
  • never: Execution never continues, regardless wether tasks are created or still unfinished.

Task instance creation might be based upon a runtime calculation. In that case, add an ActionHandler on the node-enter event of the task-node and set the attribute create-tasks="false". Here is an example of such an action handler implementation:

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);
  }
}

As shown in the example the tasks to be created can be specified in the task-node. They could also be specified in the process-definition and fetched from the TaskMgmtDefinition. TaskMgmtDefinition extends the ProcessDefinition with task management information.

The API method for marking task instances as completed is TaskInstance.end(). Optionally, you can 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.

11.3. Assignment

A process definition contains task nodes. A task-node contains zero or more tasks. Tasks are a static description as part of the process definition. At runtime, tasks result in the creation of task instances. A task instance corresponds to one entry in a person's task list.

With jBPM, push (personal task list) and pull (group task list) model (see below) of task assignment can be applied in combination. The process can calculate the responsible for a task and push it in his/her tasklist. Or alternatively, a task can be assigned to a pool of actors, in which case each of the actors in the pool can pull the task and put it in the actor's personal tasklist.

11.3.1. Assignment interfaces

Assigning task instances is done via the interface AssignmentHandler:

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 can be assigned to one or more actors. The AssignmentHandler implementation should call the Assignable methods (setActorId or setPooledActors) to assign a task. The Assignable is either a TaskInstance or a SwimlaneInstance (=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).

Each task in the process definition can be associated with an assignment handler implementation to perform the assignment at runtime.

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

To allow for the creation of reusable AssignmentHandlers, each usage of an AssignmentHandler can be configured in the processdefinition.xml. See Section 18.2, “Delegation” for more information on how to add configuration to assignment handlers.

11.3.2. The assignment data model

The datamodel 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 11.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 that can become responsible if they would take the task. Both actorId and pooledActors are optional and can also be combined.

11.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 with the property actorId on a TaskInstance. So to put a TaskInstance in someone's personal task list, you just use one of the following ways:

  • Specify an expression in the attribute actor-id of the task element in the process
  • Use TaskInstance.setActorId(String) from anywhere in your code
  • Use assignable.setActorId(String) in an AssignmentHandler

To fetch the personal task list for a given user, use TaskMgmtSession.findTaskInstances(String actorId).

11.3.4. The group task list

The pooled actors denote the candidates for the task instance. This means that the task is offered to many users and one candidate has to step up and take the task. Users can not start working on tasks in their group task list immediately. That would result in a potential conflict that many people start working on the same task. To prevent this, users can only take task instances of their group task list and move them into their personal task list. Users are only allowed to start working on tasks that are in their personal task list.

To put a taskInstance in someone's group task list, you must put the user's actorId or one of the user's groupIds in the pooledActorIds. So specify the pooled actors, use one of the following:

  • 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, proceed as follows: Make a collection that includes the user's actorId and all the ids of groups that the user belongs to. With TaskMgmtSession.findPooledTaskInstances(String actorId) or TaskMgmtSession.findPooledTaskInstances(List actorIds) you can search for task instances that are not in a personal task list (actorId==null) and for which there is a match in the pooled actorIds.

The motivation behind this is that we want to separate the identity component from jBPM task assignment. jBPM only stores Strings as actorIds and doesn't know the relation between the users, groups and other identity information.

The actorId will always override the pooled actors. So a taskInstance that has an actorId and a list of pooledActorIds, will only show up in the actor's personal task list. Keeping the pooledActorIds around allows a user to put a task instance back into the group by just erasing the actorId property of the taskInstance.

11.4. Task instance variables

A task instance can have its own set of variables and a task instance can also 'see' the process variables. Task instances are usually created in an execution path (=token). This creates a parent-child relation between the token and the task instance similar to the parent-child relation between the tokens themselves. The normal scoping rules apply between the variables of a task instance and the process variables of the related token. More info about scoping can be found in Section 10.4, “Variables scopes”.

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.

11.5. Task controllers

At creation of a task instance, the task controllers can populate the task instance variables and when the task instance is finished, the task controller can submit the data of the task instance into the process variables.

Note that you are not forced to use task controllers. Task instances also are able to 'see' the process variables related to its token. Use task controllers when you want to:

  • a) create copies of variables in the task instances so that intermediate updates to the task instance variables don't affect the process variables untill the process is finished and the copies are submitted back into the process variables.
  • b) the task instance variables do not relate one-on-one with the process variables. E.g. suppose the process has variables 'sales in januari' 'sales in februari' and 'sales in march'. Then the form for the task instance might need to show the average sales in the 3 months.

Tasks are intended to 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.

The task controller makes the translation (if any) from the process variables to the task variables. When a task instance is created, the task controller is responsible for extracting information from the process variables and creating the task variables. The task variables serve as the input for the user interface form. And the user input can be stored in the task variables. When the user ends the task, the task controller is responsible for updating the process variables based on the task instance data.

The task controllers

Figure 11.2. The task controllers

In a simple scenario, there is a one-on-one mapping between process variables and the form parameters. Task controllers are specified in a task element. In this case, the default jBPM task controller can be used and it takes a list of variable elements inside. The variable elements express how the process variables are copied in the task variables.

The next example shows how you can create separate task instance variable copies 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 also is used as the label for the fields in the task instance form of the web application.

The access attribute specifies if the variable is copied at task instance creation, will be written back to the process variables at task end and wether it is required. 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 and a start-state can have 1 task.

If the simple one-to-one mapping between process variables and form parameters is too limiting, you can also write your own TaskControllerHandler implementation. Here's the TaskControllerHandler interface:

public interface TaskControllerHandler extends Serializable {
  void initializeTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
  void submitTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
}

And here's how to configure your custom task controller implementation in a task:

<task name="clean ceiling">
  <controller class="com.yourcom.CleanCeilingTaskControllerHandler">
    -- here goes your task controller handler configuration --
  </controller>
</task>

11.6. Swimlanes

A swimlane is a process role. It is a mechanism to specify that multiple tasks in the process should be done by the same actor. So after the first task instance is created for a given swimlane, the actor should be remembered in the process for all subsequent tasks that are in the same swimlane. A swimlane therefore has one assignment and all tasks that reference a swimlane should not specify an assignment.

When the first task in a given swimlane is created, the AssignmentHandler of the swimlane is called. The Assignable that is passed to the AssignmentHandler will be the SwimlaneInstance. Important to know is that all assignments that are done on the task instances in a given swimlane will propagate to the swimlane instance. This behaviour is implemented as the default because the person that takes a task to fulfilling a certain process role will have the knowledge of that perticular process. So all subsequent assignements of task instances to that swimlane are done automatically to that user.

Swimlane is a terminology borrowed from UML activity diagrams.

11.7. Swimlane in start task

A swimlane can be associated with the start task to capture the process initiator.

A task can be specified in a start-state. That task be associated with a swimlane. When a new task instance is created for such a task, the current authenticated actor will be captured with Authentication.getAuthenticatedActorId() and that actor will be stored in the swimlane of the start task.

For example:

<process-definition>
  <swimlane name='initiator' />
  <start-state>
    <task swimlane='initiator' />
    <transition to='...' />
  </start-state>
  ...
</process-definition>

Also variables can be added to the start task as with any other task to define the form associated with the task. See Section 11.5, “Task controllers”

11.8. Task events

Tasks can have actions associated with them. There are 4 standard event types defined for tasks: task-create, task-assign, task-start and task-end.

task-create is fired when a task instance is created.

task-assign is fired when a task instance is being assigned. Note that in actions that are executed on this event, you can access the previous actor with executionContext.getTaskInstance().getPreviousActorId();

task-start is fired when TaskInstance.start() is called. This can be used to indicate that the user is actually starting to work on this task instance. Starting a task is optional.

task-end 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 resuming of the process execution.

Since tasks can have events and actions associated with them, also exception handlers can be specified on a task. For more information about exception handling, see Section 9.7, “Exception handling”.

11.9. Task timers

As on nodes, timers can be specified on tasks. See Section 13.1, “Timers”.

The special thing about timers for tasks is that the cancel-event for task timers can be customized. By default, a timer on a task will be cancelled when the task is ended (=completed). But with the cancel-event attribute on the timer, process developers can customize that to e.g. task-assign or task-start. The cancel-event supports multiple events. The cancel-event types can be combined by specifying them in a comma separated list in the attribute.

11.10. Customizing task instances

Task instances can be customized. The easiest way to do this is to create a subclass of TaskInstance. Then create a org.jbpm.taskmgmt.TaskInstanceFactory implementation and configure it by setting the configuration property jbpm.task.instance.factory to the fully qualified class name in the jbpm.cfg.xml. If you use a subclass of TaskInstance, also create a hibernate mapping file for the subclass (using the hibernate extends="org.jbpm.taskmgmt.exe.TaskInstance"). Then add that mapping file to the list of mapping files in the hibernate.cfg.xml

11.11. The identity component

Management of users, groups and permissions is commonly known as identity management. jBPM includes an optional identity component that can be easily replaced by a company's own identity data store.

The jBPM identity management component includes knowledge of the organisational model. Task assignment is typically done with organisational knowledge. So this implies knowledge of an organisational model, describing the users, groups, systems and the relations between them. Optionally, permissions and roles can be included too in an organisational model. Various academic research attempts failed, proving that no generic organisational model can be created that fits every organisation.

The way jBPM handles this is by defining an actor as an actual participant in a process. An actor is identified by its ID called an actorId. jBPM has only knowledge about actorId's and they are represented as java.lang.Strings for maximum flexibility. So any knowledge about the organisational model and the structure of that data is outside the scope of the jBPM 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. People interested in contributing should check the jboss jbpm jira issue tracker for more details.

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 organisational structure of users involved in a process.

11.11.1. The identity model

The identity model class diagram

Figure 11.3. The identity model class diagram

The classes in yellow are the relevant classes for the expression assignment handler that is 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. haircolor 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 fullfills in the group.

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

Syntax of the assignment expression is like this:

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)

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

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

11.11.3. Removing the identity component

When you want to use your own datasource for organisational information such as your company's user database or ldap system, you can just rip out the jBPM identity component. The only thing you need to do is make sure that you delete the line ...

<mapping resource="org/jbpm/identity/User.hbm.xml"/>
<mapping resource="org/jbpm/identity/Group.hbm.xml"/>
<mapping resource="org/jbpm/identity/Membership.hbm.xml"/>

from the hibernate.cfg.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);