第11章. 任务管理

来源:互联网 发布:react router 传数据 编辑:程序博客网 时间:2024/05/17 04:05

第11章. 任务管理

 JBPM的核心业务是持久化流程执行的能力. 对任务管理和个人的任务清单来说这个特性是非常有用的功能。jBPM 允许指明一段软件描述所有人的任务中处于等待状态的流程。

11.1. Tasks

任务是流程定义的一部分,并且他们定义在流程执行期间任务实例如何必须被创建和分配。

任务可以在task-nodes和process-definition中被定义。最常用的方式是在task-node中定义一个或多个任务。在此情况下,task-node表示一个可被用户处理的任务并且流程执行将等待直到操作者完成任务。当操作者完成任务时流程执行将继续。当在一个Task-node上指定多个任务时,默认的行为是等到所有的任务完成。

任务也可以 process-definition上指定。在流程定义上指定的任务可通过名字查找并在task-nodes中引用或用在action内。实际上,在流程定义中所有给定了名字的任务 (also in task-nodes)都能通过名字查找。

任务名必须在整个流程定义中是唯一的。任务可指定优先级。这个优先权将被使用在初始化优先权为每个为任务创建的流程实例。任务实例然后可以修改初始化优先权。

11.2. 任务实例

一个任务实例可以被分配给一个actorId (java.lang.String)。所有的任务实列都被保存在数据库中的表jbpm_taskinstance里。当你想得到特定用户的任务清单时,你就可以通过一个与用户关联的 actorId来查询这张表.

这个jBPM任务列表机制可以组合jBPM任务和其他任务,甚至当这些任务与流程执行无关。那种方法jBPM开发者可以很容易的在一个集中的任务列表仓库中组合jBPM流程任务和其他应用程序。

11.2.1. 任务实例生命周期

任务实例生命周期是简单的:创建之后,任务实例可以随意地开始。接着,任务实例可以被终结,这意味着任务实例被标志为完成。

注意:为了灵活性,委派不是生命周期的组成部分。所以任务实例可以被委派或者不委派。任务实例委派在生命周期任务实例中没有影响。

任务实例通常由流程执行进入一个任务节点(task-node)来创建的(使用方法 TaskMgmtInstance.createTaskInstance(...)).然后, 一个用户接口组件将用TaskMgmtSession.findTaskInstancesByActorId(...) 查询数据库得到任务列表。然后, 在收集了用户的输入后,UI组件调用 TaskInstance.assign(String), TaskInstance.start()或 TaskInstance.end(...)。

一个任务实例通过借助于日期属性来维护它的状态 : create, startend. 这些属性可以在任务实例中通过他们各自的getter来访问。

目前, 已完成的任务实例以一个结束日期来标示,所以它们不能被随后的任务列表查询获得.但它们依然保存在JBPM_TASKINSTANCE表里。

11.2.2. 任务实例和图形执行

任务实例是有操作者的任务列表中的条目。任务实例可发射信号.一个信令任务实例就是一个任务实例在完成的时候可以发送一个信号给它的令牌去继续流程执行. 任务实例可以被阻塞,意思是说相关的令牌(= 执行路线)在任务实例被完成之前不允许离开 task-node.默认的任务实例是信令(signalling )和非阻塞的(non-blocking).

在多个任务实例同一个task-node关联情况下,流程开发者可以指定怎样完成任务实例来影响流程的继续? . 下面是一个给task-node的singal-property的值的清单.

  • last: 这是默认的. 当最后的任务实例完成时候继续执行下去。 当在这个节点的入口没有任务被创建时,执行继续.
  • last-wait:当最后的任务实例完成时候继续执行下去。 当这个节点入口没有任务被建立时,任务节点执行等待直到任务被建立.
  • first:当第一个任务实例被完成时候获得执行. 当在这个节的入口没有任务被生成时候,执行继续.
  • first-wait:当第一个任务实例被完成时候获得执行. 当在这个节点的入口没有任务被生成时候,任务节点执行等待直到任务被建立.
  • unsynchronized: 执行一直继续,不管任务是否没建立或依然没有完成.
  • never: 执行永不继续, 不管任务是否建立或依然没有完成.

任务实例建立必须基于以上运行时间计算. 这种情况下,在task-node的 node-enter事件里增加一个 ActionHandler并且设置属性 create-tasks="false" . 下面是一个动作handler的实现:

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

// 现在, 对于同一个任务创建了2个任务实例.
tmi.createTaskInstance(changeNappy, token);
tmi.createTaskInstance(changeNappy, token);
}
}

如例子所展示,在task-node指定的任务被建立. 它们也可以在 process-definition中指定并且通过 TaskMgmtDefinition来获取. TaskMgmtDefinition用任务管理信息来扩展(extends) ProcessDefinition .

标记任务实例的API完成用 TaskInstance.end() .随意地,你可以指定一个转换在end 方法里. 这种情况下这个任务实例的完成将触发执行的继续,task-node 通过指定的转换离开..

11.3. 分配

流程定义包括任务节点。task-node包括0或多个任务。任务是流程定义中的静态描述部分。在运行时, 任务结果在任务实例的创建中. 一个任务实例对应着个人任务列表里的一个入口.

在jBPM, 任务分派的推和 推和 拉 模式 (看下面) 可以组合使用. 流程能计算任务的责任并把它"推"到他/她的任务清单里(tasklist).或者另外的方法, 把任务分配给参与者池, 这种情况下池中的每一个参与者可以"拉"任务并且把它们放到参与者个人的任务清单中(tasklist).

11.3.1. Assignment interfaces

分配任务实例是通过AssignmentHandler接口来做的:

public interface AssignmentHandler extends Serializable {
void assign( Assignable assignable, ExecutionContext executionContext );
}

一个AssignmentHandler实现在当任务实例被创建时调用。在此时,任务实例可以被分配给一个或多个操作者。 AssignmentHandler实现将调用Assignable的方法(setActorId or setPooledActors)来分配任务. The Assignable是一个TaskInstance或SwimlaneInstance (=流程角色).

public interface Assignable {
   public void setActorId(String actorId);
   public void setPooledActors(String[] pooledActors);
}

TaskInstances 和SwimlaneInstances都可被分配给一个特定的用户或一个操作组池(pool). 要将任务实例分配给一个用户,调用Assignable.setActorId(String actorId). 要将一个任务实例分配给一个候选操作者池, 调用 Assignable.setPooledActors(String[] actorIds).

流程定义中的每个任务可同一个assignment handler实现相关联来在运行时执行分派.

当在流程中有多于一个的任务被分配给同一个人或操作者组时,建议使用swimlane

为了允许建立可重用的 AssignmentHandler,每个 AssignmentHandler的用途可以在 processdefinition.xml中配置.参看 章节?13.2, “代理” 更多有关怎么添加分配handler的信息.

11.3.2. 分配数据模型

任务实例的管理分配和分配swimlance实例到参与者的数据模型如下. 每个 TaskInstance 有actorId和一组pooled actors.

The assignment model class diagram

Figure 11.1. The assignment model class diagram

actorId对任务负责, 当一组pooled actors代表候选者集合,如果他们得到任务就可以负责.actorId 和pooledActors 可以任意的组合.

11.3.3. Push model

任务实例的actorId 表明对指定任务责任的. TaskInstance的pooled actors是任务参与者的候选人. 典型的,TaskInstance 的actorId将引用一个用户. pooled actors可以引用用户和组.

用户的个人任务列表是所有拥有指定用户actorId作为 TaskInstance.这个清单的获得是用过 TaskMgmtSession.findTaskInstances(String actorId).

11.3.4. Pull model

另外一面,给指定用户的共享任务是用来给从共有的参与者中引用的用户。获取共享任务典型两个步骤操作: 1)从身份组件得到给定用户所有的组2)根据用户的actorId和从用户组得到的actorId来得到所有共有的任务.得到给定用户的所有共有的任务的清单通过方法 TaskMgmtSession.findPooledTaskInstances(String actorId) 或 TaskMgmtSession.findPooledTaskInstances(List actorIds) . 这些方法只返回任务实例的actorId是null或给定actorId之一匹配共有的的参与者其中一个.

为了防止多个用户在同一个共有的任务( pooled task)上工作, 更新TaskInstance的actorId为用户的actorId就可以了. 做了这个之后, 任务实例将不会显示在共有的任务清单里,而只在用户的个人任务清单里. 设置TaskInstance 的actorId 为null,将会把任务实例放回共有的任务里.

11.4. 任务实例变量

任务实例可有其自已的一组变量并且任务实例也可以看到流程变量。任务实例通常是在执行路径(=令牌)中创建。与令牌之间的父子关系相似,这会在令牌和任务实例之间创建一个父子关系。正常的范围规则适用于任务实例变量和同令牌相关的流程变量,有关范围的更多信息可以在“10.4 变量范围”找到

这意味着任务实例可以“看到”它自己的变量,另外还有它相关的令牌的所有变量。

控制器可以用来在任务实例范围和流程范围的变量间创建、组装和提交变量。

11.5.任务控制器

在任务实例的创建时,任务控制器可计算任务实例变量并且当任务实例结束时,任务控制器可提交任务实例的数据到流程变量中。

注意:并没有强迫你使用任务控制器。 任务实例也可看到关联到其令牌的流程变量。当你想做以下事情时使用任务控制器:

  • a) 创建任务实例中变量的拷贝以便能立即更新任务实例的变量而不影响流程变量,直到流程结束并且拷贝被 提交回流程变量中.
  • b) 任务实例变量同流程变量不是一对一的关系。例如,假设流程有变量“sales in januari”“sales in februari”和“sales in march”,而任务实例所使用表单可能需要显示的是三个月的平均销售量。

任务用来收集用户输入,但是目前有很多可以向用户展示任务的用户界面,例如web应用、swing应用、及时消息、电子邮件表单…因此任务控制器在流程变量(=流程上下文)和用户界面应用之间起到了桥的作用,任务控制器为用户界面应用提供流程变量的视图。

The task controller makes the translation (if any) from the 流程变量 to the task variables. When a 任务实例 is created, the task controller is responsible for extracting information from the 流程变量 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 流程变量 based on the 任务实例 data.

任务控制器在流程变量到任务变量之间进行转换(如果需要)。当任务实例被创建时,任务实例负责从流程变量提取信息,并且创建任务变量,任务变量作为用户接口表单是输入,并且用户输入可以存储在任务变量里;当用户结束任务时,任务控制器负责基于任务实例数据更新流程变量。

The task controllers

Figure 11.2.任务控制器

In a simple scenario, there is a one-on-one mapping between 流程变量 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 流程变量 are copied in the task variables.

简单的情形是,在流程变量和表单参数之间是一对一的映射,任务控制器在task元素中指定,这种情况下,默认的Jbpm任务管理器可以被使用,它包含一个variable元素列表,variable元素表示流程变量怎样被拷贝到任务变量。

下例显示如何基于流程变量来创建独立的任务实例变量拷贝:

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

name属性指向流程变量的名字mapped-name是可选的,指向任务实例变量的名字。 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 任务实例 form of the web application.

name属性指向流程变量的名称,mapped-name属性是任意的,用来指向任务实例变量的名称。如果忽略mapped-name属性,则mapped-name默认与name属性相同。注意,mapped-name也被用来在web应用中作为任务实例表单的域标签。

The access attribute specifies if the variable is copied at 任务实例 creation, will be written back to the 流程变量 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'.

access属性指定了如果在任务实例创建时变量被拷贝,是否需要在任务结束时把它写回流程变量。这个信息可以被用户接口所使用,进行适当的表单控制。access属性是可选的,默认值是“read,write”。

任务节点(task-node)可以拥有多个任务,而开始状态(start-state)只能有一个任务。

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

如果在流程变量和表单参数之间简单的一对一映射太过约束,你也可以编写你自己的TaskControllerHandler实现,下面是TaskControllerHandler接口:

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 配置 --
</controller>
</task>

11.6. 泳道(Swimlanes)

一个swimlane是一个流程角色。它是一种用于指定流程中的多个任务将被同一个行动者处理的机制。所以在第一个任务实例为给定的swimlane建立后,参与者将被同一个流程里所有随后都处于同一个swimlane中的任务记住。因此swimlane 有一个 assignment 并且所有引用一个swimlane的任务将不会指定一个 assignment.

当在给定的泳道上的第一个任务被建立时,swimlane 的 AssignmentHandler 被调用。传递到 AssignmentHandler的Assignable 将会成为 SwimlaneInstance . 需要知道的重点是所有分派是在任务实例上将传播到swimlane实例的给定的swimlane中完成。这个行为被实现作为默认的,因为个人将作为必要的流程角色履行任务.因此所有以后分派到swimlane的任务实例自动到那个用户。

Swimlane 是从UML活动图中借来的术语.

11.7. 开始任务中的泳道

泳道可以与开始任务关联,用以捕获流程的发起者。

在start-state可定义一个任务。该任务与泳道关联,当这个任务的一个新的任务实例被创建时,当前已经过鉴定的参与者可以使用Authentication.getAuthenticatedActorId()(参考17.2 鉴定)获取(译者注:这时创建开始任务时,自动进行的),并且该参与者存储在开始任务的泳道里。

For example:

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

象其它任何任务一样,也可以向开始任务添加变量用来定义与任务关联的表单。请参考 Section 11.5, “Task controllers”

11.8. 任务事件

任务有动作关联.对任务定义了四种标准的事件类型: task-create, task-assign, task-starttask-end.

task-create 在任务实例创建时触发.

task-assign 在任务实例被分配时点燃(fired). 注意在此事件上执行的动作中,你可以使用executionContext.getTaskInstance().getPreviousActorId()访问前一个行动者(actor);

task-start 是当 TaskInstance.start()被调用时产生.这通常用于暗示真正开始在此任务实例上开始工作的用户. 启动任务是可选的.

task-end 当 TaskInstance.end(...)被调用时产生 .这标记着任务的完成. 如果任务同流程执行相关, 这个调用将触发流程执行继续.

因为任务有事件和动作同其关联, 也可在任务上指定异常处理器. 有关异常处理的更多信息请见Section 9.7, “异常处理”.

11.9. 任务定时器

作为节点,可在任务上指定定时器. 参见 Section 12.1, “Timers”.

对于任务的定时器需要指明的是:任务定时器的cancel-event可以被定制。默认情况下,当任务被结束时(=完成)任务上的定时器将被取消,但是通过在定时器上使用cancel-event属性,流程开发者可以定制诸如task-assign或task-start。cancel-event支持多个事件. cancel-event 类型能被用逗号分隔的多个事件列表.

11.10. 定制任务实例

任务实例可被定制. 最简单的方法是创建TaskInstance的一个子类. 然后创建 org.jbpm.taskmgmt.TaskInstanceFactory的实现并且通过在jbpm.cfg.xml中指定属性jbpm.task.instance.factory至全权限类名来配置它。如果你使用TaskInstance的子类,也要为此子类创建一个hibernate配置文件(using the hibernate extends="org.jbpm.taskmgmt.exe.TaskInstance"). 然后将映射文件加入到hibernate.cfg.xml中的映射文件的列表中.

11.11. 身份组件

用户管理,组和权限管理一般都称做身份管理. jBPM 包括可选的身份组件,可以用你公司自己的身份存储数据来代替.

jBPM 身份管理组件包括组织模型的知识. 任务分配典型的根据组织知识来完成. 因此这个隐含的组织知识模型,描述了用户,组,系统和它们之间的关系. 任意的,权限和角色也可以包含在组织模型中.数个学术研究尝试失败,证明没有通用的组织模型可以用来适合所有的组织.

jBPM 处理的方法是定义参与者作为实际的流程的实际参与者。一个参与者用它叫做actorId的ID来标识。 jBPM 只有关于actorId的知识并且为了灵活性他们表示为 java.lang.String. 因此任何关于组织模型和数据结构的知识都不在jBPM 核心引擎之内.

作为jBPM的一个扩充,我们会提供(在将来)组件来管理简单的用户-角色模型. 这个用户和角色之间多对多的关系同J2EE和servlet规范中定义的一致因此他能作为一个新的开发开始点 .可以检查jbpm jira问题来追踪更多的细节.

注意用在servlet,ejb和portlet规范里的用户-角色模型,是不足以处理任务分派。在用户和角色之间模型是多对多关系。这不包含在流程中涉及的用户的任何关于team以及组织结构的信息。

11.11.1. 身份模型

The identity model class diagram

Figure 11.3. 身份模型类图

黄色类图是同下一节将讨论的表达分配处理器(expression assignment handler)有关类.

一个User类表示一个用户或一个服务。一个Group是某种类型的用户的组合.组能嵌套进同team,业务单位和整个公司相关的模型中。组有类型来区分组层次,比如头发颜色组. Memberships 表示用户和组之间的多对多关系.membership可用来表示在公司中的职位. 成员的名字可以用来指明用户在组里履行的角色.

11.11.2. 分配表达式

身份(identity)组件带有一个在任务分配期间求操作者的运算的表达式值。这儿有一个在流程定义中使用分配表达式的样例:

<process-definition>
...
<task-node name='a'>
<task name='laundry'>
<assignment expression='previous --> group(hierarchy) --> member(boss)' />
</task>
<transition to='b' />
</task-node>
...

分配表达式的语法象这样:

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

表达式是从左到右解析的. 第一个条款( first-term)指定身份模型中一个用户或一个组. Subsequent terms calculate the 下一 term from the intermediate user or group.以后的术语从中间的用户或组来计算下一个术语.

previous意昧着任务被分配给当前认证的操作者. 这意昧着操作者执行流程中的上一步.

swimlane(swimlane-name) 意昧着从指定的swimlane实例中取得用户或组.

variable(variable-name) 意昧着从指定的变量实例中取得用户或组。 变量实例包含java.lang.String,这种情况下用户或组从身份组件中获得。或者变量实例 包含用户或组对象.。

user(user-name) 意昧着从身份组件中取得给定的用户

group(group-name)意昧着从身份组件中取得给定的组。

11.11.2.2. Next terms

group(group-type) 取得一个用户的组. 意昧着前一个条款必须有一个User结果。它根据给定的group-type搜索组在所有的用户成员里.

member(role-name) 从组里得到用户执行的给定角色.前一个术语(previous terms) 必须有一个组结果。这个术语在组中为用户搜索与给顶的role-name相匹配的membership。

11.11.3. 移走身份组件

如果你想使用自已的组织信息的数据源如公司的用户数据库或ldap系统, 你只需移除jBPM标识组件. 你唯一需要做的事情是确保你已经从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"/>

由于ExpressionAssignmentHandler依赖标识组件所以象这样做你就不能使用它. 如果你想复用 ExpressionAssignmentHandler并且同你的用户数据绑定, 你可以扩展ExpressionAssignmentHandler并且覆盖getExpressionSession方法.

protected ExpressionSession getExpressionSession(AssignmentContext assignmentContext);
原创粉丝点击