jBPM用户指南 第十一章 任务管理 (转载)

来源:互联网 发布:nginx 反向代理优化 编辑:程序博客网 时间:2024/05/15 23:46
第11章 任务管理
Jbpm的核心业务是持久化流程执行的能力,对于管理任务和个人任务清单来说这是一个非常重要的特性,Jbpm允许指定一段软件描述所有人的任务中处于等待状态的流程。
11.1 任务
任务是流程定义的一部分,它们定义了在流程执行期间任务实例怎样被创建和分配。
任务可以在task-node和process-definition中定义,通常使用的方式是在一个task-node里定义一个或多个任务,这种情况下,task-node代表一个将由用户完成的任务,并且流程执行将一直等待直到参与者完成这个任务,当参与者完成任务时,流程执行将继续。当在一个task-node中指定多个任务时,默认的行为是等待所有任务完成。
任务也可以被指定在process-definition。指定在process-definition的任务可以通过名称查找到并且在task-node里引用或者在内部动作中使用。事实上,所有给定名称的任务(包括在task-node中定义的任务)都可以在流程定义(process-definition)中通过名字查找到。
任务名称在整个流程定义中必须是唯一的。任务可以被指定一个优先级,这个优先级在任务的实例创建时将被作为每个任务实例的初始优先级,任务实例的初始优先级可以在以后被修改。
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(…)。
任务实例依靠日期属性维护它的状态:创建、开始和结束。这些属性可以通过任务实例上它们各自的getter方法访问。
通常,完成的任务实例用一个结束日期做了标记,所以它们不能被以后的任务列表查询获取,但是它们仍然存在于JBPM_TASKINSTANCE表中。
11.2.2 任务实例和图执行
任务实例是参与者任务清单(tasklist)中的项目,任务实例可以作为信号,当一个信号任务实例完成时,可以发送一个信号到它的令牌继续流程执行。任务实例可以被阻塞,这意味着在任务实例完成之前相关令牌(=执行路径)不允许离开任务节点。默认情况下,任务实例是信号非阻塞的。
如果多于一个任务实例与一个任务节点关联,流程开发者可以指定任务实例的完成怎样影响流程的继续。下面是可以给任务节点的signal属性设置的值:
l        last:这是默认值。当最后一个任务实例完成时继续执行;当在节点入口处没有任务创建时,继续执行。
l        last-wait:当最后一个任务实例完成时继续执行;当在节点入口处没有任务创建时,执行在任务节点等待,直到任务被创建。
l        first:当第一个任务实例完成时继续执行;当在节点入口处没有任务创建时,继续执行。
l        first-wait:当第一个任务实例完成时继续执行;当在节点入口处没有任务创建时,执行在任务节点等待,直到任务被创建。
l        unsynchronized:总是继续执行,不管任务是否创建和完成。
l        never:执行不再继续,不管任务是否创建和完成。
任务实例可以基于运行时的计算创建,如果那样的话,需要添加一个ActionHandler到任务节点的node-enter事件,并且设置属性create-tasks=“false”。下面是这样一个动作的实现例子:
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");
 
    // 现在, 相同任务的两个任务实例被创建
    tmi.createTaskInstance(changeNappy, token);
    tmi.createTaskInstance(changeNappy, token);
 }
}
如示例所展示,任务可以在指定的任务节点中创建,它们也可以被指定到process-definition,并且从TaskMgmtDefinition获取。TaskMgmtDefinition用任务管理信息扩展了ProcessDefinition。
标识任务示例完成的API是TaskInstance.end(),你可以在end方法中指定一个转换,如果这个任务的完成会触发继续执行,则会通过指定的转换离开任务节点。
11.3 分配
流程定义包含任务节点,任务节点(task-node)包含一个或多个任务,任务作为流程定义的一部分是静态描述。在运行时,任务导致任务实例的创建,一个任务实例对应某人任务列表中的一个入口。
在Jbpm中,可以结合使用推模式拉模式(见下文)的任务分配。流程可以计算任务的责任人,并把它推到他/她的任务清单里;或者,任务可以被分配到参与者池,这种情况下,池中的每个参与者都可以拉出任务并把它放入参与者的个人任务清单。
11.3.1 分配接口
通过接口AssignmentHandler进行任务实例分配:
public interface AssignmentHandler extends Serializable {
 void assign( Assignable assignable, ExecutionContext executionContext );
}
当任务实例被创建时分配处理的实现被调用,在那时,任务实例可以被分配到一个或多个参与者。AssignmentHandler实现将调用分配方法(setActorId或setPooledActors)分配任务,可以分配一个任务实例或者一个泳道实例swimlaneInstance(=流程角色)。
public interface Assignable {
 public void setActorId(String actorId);
 public void setPooledActors(String[] pooledActors);
}
任务实例和泳道实例都可以被分配到一个用户或者共享参与者。分配任务实例到一个用户,调用Assignable.setActorId(String actorId);分配任务实例到候选的共享参与者,调用Assignable.setPooledActors(String[] actorIds)。
流程定义中的每个任务都可以与一个分配处理实现相关联,用来完成运行时的任务分配。
当流程中的多个任务将要分配给相同的人或者参与者组时,考虑使用泳道
考虑到AssignmentHandler的重用,每个AssignmentHandler的使用可以在processdefinition.xml中配置。请参考“16.2 委托”了解怎样添加分配处理配置的更多信息。
11.3.2 分配数据模型
下面是管理分配任务实例和泳道实例到参与者的数据模型,每个任务实例拥有一个actorId或一组被共享的参与者。
11.1 分配模型类图
actorId对任务负责,而共享的参与者表示候选者集合,如果它们获取任务,则可以负责任务。actorId和共享参与者具体使用哪个是可选的,两者也可以结合使用。
11.3.3 推模式
任务实例的actorId表明对给定任务负责,而任务实例的共享参与者是任务的候选参与者。典型情况下,任务实例的actorId指向一个用户,共享参与者可以指向多个用户和/或用户组。
用户的任务清单是所有以用户作为actorId的任务实例,这个清单可以使用TaskMgmtSession.findTaskInstances(String actorId)获得。
11.3.4 拉模式
另一方面,共享的任务(译者注:共享任务即不仅是由一个用户负责的)是提供给共享参与者中所引用的用户的。获取共享任务一般需要两步操作:1)从身份组件中获取给定用户的所有组2)为结合了用户的actorId和指向用户组的actorId获取所有共享任务清单(译者注:这段话不太好理解,解释如下――任务指向一个actorId,在Jbpm中并没有强制限制该actorId必须为单个用户或者用户组,所以我们在实际应用中也可以把actorId作为一个用户组。假如有一个用户为user1,他属于用户组group1,在流程中有些任务分配到了user1,而其它又有些任务分配到了group1,如果我们要取user1的所有共享任务,则即需要获取分配到user1的共享任务,也需要获取分配到group1的共享任务)。可以使用方法TaskMgmtSesion.findPooledTaskInstances(String actorId)或TaskMgmtSession.findPooledTaskInstances(List actorIds)获取提供给指定用户的共享任务清单,这些方法会返回actorId为空(null)以及共享参与者中某个与给定的actorIds中的某个相匹配的任务实例。
为了防止多个用户在同一个共享任务上工作,使用用户的actorId修改任务实例的actorId就可以了。这样,任务实例将不会出现在共享任务清单中,而只会存在于用户个人的任务清单里。设置任务实例的actorId为空(null),则会把任务实例放回共享任务里。
11.4 任务实例变量
任务实例可以拥有它自己的变量,并且也可以“看到”流程变量。任务实例通常是在执行路径(=令牌)中创建,与令牌之间的父子关系相似,这会在令牌和任务实例之间创建一个父子关系。正常的范围规则适用于相关令牌的任务实例变量和流程变量之间,有关范围的更多信息可以在“10.4 变量”找到。
这意味着任务实例可以“看到”它自己的变量,另外还有它所关联令牌的所有变量。
控制器可以用来在任务实例范围和流程范围的变量间创建、组装和提交变量。
11.5 任务控制器
在任务实例创建时,任务控制器可以组装任务实例变量,并且当任务实例完成时,任务控制器可以提交任务实例的数据到流程变量。
注意,任务控制器不是强制使用的,即使不使用任务控制器,任务实例也能够“看到”与它的令牌相关的流程变量,当你想要做如下事情时使用任务控制器:
l        在任务实例中创建变量的拷贝,这样任务变量的中间更新不会影响到流程变量,而是直到处理完成后拷贝才被提交回流程变量。
l        任务实例变量与流程变量不是一一对应的。例如,假设流程有变量“sales in januari”“sales in februari”和“sales in march”,而任务实例所使用表单可能需要显示的是三个月的平均销售量。
任务用来收集用户输入,但是目前有很多可以向用户展示任务的用户接口,例如web应用、swing应用、及时消息、电子邮件表单…因此任务控制器在流程变量(=流程上下文)和用户接口应用之间起到了桥的作用,任务控制器为用户接口应用提供流程变量的视图。
任务控制器在流程变量到任务变量之间进行转换(如果需要)。当任务实例被创建时,任务实例负责从流程变量提取信息,并且创建任务变量,任务变量作为用户接口表单是输入,并且用户输入可以存储在任务变量里;当用户结束任务时,任务控制器负责基于任务实例数据更新流程变量。
11.2 任务控制器
简单的情形是,在流程变量和表单参数之间是一对一的映射,任务控制器在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属性是任意的,用来指向任务实例变量的名称。如果忽略mapped-name属性,则mapped-name默认与name属性相同。注意,mapped-name也被用来在web应用中作为任务实例表单的域标签。
access属性指定了如果在任务实例创建时变量被拷贝,是否需要在任务结束时把它写回流程变量。这个信息可以被用户接口所使用,进行适当的表单控制。access属性是可选的,默认值是“read,write”。
任务节点(task-node)可以拥有多个任务,而开始状态(start-state)只能有一个任务。
如果在流程变量和表单参数之间简单的一对一映射太过约束,你也可以编写你自己的TaskControllerHandler实现,下面是TaskControllerHandler接口:
public interface TaskControllerHandler extends Serializable {
 void initializeTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
 void submitTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
}
下面展示了怎样在任务中配置你自己定制的任务控制器:
<task name="clean ceiling">
 <controller class="com.yourcom.CleanCeilingTaskControllerHandler">
    -- here goes your task controller handler configuration --
 </controller>
</task>
 
11.6 泳道(swimlane)
泳道是流程角色,它是定义流程中的多个任务由相同参与者完成的一种机制。在第一个任务实例为指定泳道创建后,参与者将被流程记住,以被在同一泳道中的后续任务所使用。泳道有一个分配,因此所有引用泳道的任务不需要再指定分配
当给定泳道的第一个任务被创建时,泳道的AssignmentHandler被调用,Assignable被传递到AssignmentHandler,将会成为泳道实例(SwimlaneInstance)。需要知道的是,给定泳道中的所有任务实例的分配都被传播到泳道实例,这个行为是被作为默认实现的,因为获取任务进行处理的人在履行某个流程角色(译者注:也就是说在某个泳道内),因此所有后续对泳道的任务实例的分配会自动转到用户。
泳道是从UML活动图中借来的术语。
11.7 开始任务中的泳道
泳道可以与开始任务关联,用以捕获流程的发起者。
任务可以在start-state中指定,该任务与泳道关联,当这个任务的一个新的任务实例被创建时,当前已经过鉴定的参与者可以使用Authentication.getAuthenticatedActorId()(参考17.2 鉴定)获取(译者注:这时创建开始任务时,自动进行的),并且该参与者存储在开始任务的泳道里。
例如:
<process-definition>
 <swimlane name='initiator' />
 <start-state>
    <task swimlane='initiator' />
    <transition to='...' />
 </start-state>
 ...
</process-definition>
象其它任何任务一样,也可以向开始任务添加变量用来定义与任务关联的表单。请参考“11.5 任务控制器”。
11.8 任务事件
任务可以拥有所关联的动作。任务有四个标准的事件类型定义:task-create,task-assign,task-start,和task-end。
当一个任务实例被创建时task-create事件被激活。
当一个任务实例被分配时task-assign事件被激活。注意:在这个事件中执行的动作里,你可以用executionContext.getTaskInstance().getPreviousActorId()访问前一个参与者。
当TaskInstance.start()被调用时task-start事件被激活。这可以被用来标识用户在这个任务实例上已经开始工作。是否开始一个任务是可选的。
当TaskInstance.end()被调用时task-end事件被激活。这标志了任务的完成,如果任务与流程执行相关,这个调用会触发流程继续执行。
因为任务可以拥有事件以及相关联的动作,所以异常处理也可以在任务上被指定。有关异常处理的更多信息,请参考“9.7 异常处理”。
11.9 任务定时器
在节点上,定时器可以在任务上被指定。请参考“12.1 定时器”。
对于任务的定时器需要指明的是:任务定时器的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映射文件(使用hibernate的extends=”org.jbpm.taskmgmt.exe.TaskInstance”),然后在hibernate.cfg.xml中添加该映射文件到映射文件列表。
11.11 身份组件
用户、组和权限管理一般都被称为身份管理。Jbpm包含了一个可选的身份组件,它可以很容易的被企业自己的身份数据存储所替换。
Jbpm的身份管理组件包含了组织知识模型。任务分配典型的需要由组织知识来完成,因此,这意味着组织知识模型描述了用户、组、系统以及它们之间的关系。作为可选的,权限和角色也可以被包含进组织模型。数个学术研究尝试的失败表明,没有一个通用的组织模型可以适用于所有组织。
Jbpm的作法是定义一个参与者作为流程的实际参与者,参与者通过它的ID进行标识,称为actorId,Jbpm只知道actorId并且为了灵活性用java.lang.String表示,因此任何组织模型知识及其数据结构都在Jbpm核心引擎之外。
作为对Jbpm的扩展,我们将提供(以后)管理简单的“用户-角色”模型的组件,用户和角色之间的多对多关系与J2EE和servlet规范中定义的模型是一致的,这可以作为一个新的开发起点,感兴趣的话可以查看jboss jbpm jira中的问题跟踪来获取更多详细资料。
注意,实际被用在servlet、ejb和portlet规范中的“用户-角色”模型不足以满足任务的分配处理,该模型在用户和角色之间是一个多对多的关系,不包括流程中用户有关的组和组织结构信息。
11.11.1 身份模型
11.3身份模型类图
黄色的类是下面将要讨论的有关表达式分配处理的相关类。
User表示用户或服务。Group是任何类型的用户组,Group可以被嵌套,用来建模一个团队、一个业务单元、以及整个公司的关系,组有类型,用来在不同等级的组之间进行区分,例如haircolor组。Membership表示用户和组之间的多对多关系,membership可以被用来表示公司中的一个职位,membership的名字可被用来表示用户在组中的角色。
11.11.2 分配表达式
随同身份组件提供了一种实现,即在进行任务分配期间通过表达式计算参与者。这里有一个在流程定义中使用分配表达式的例子:
<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-term
表达式以从左到右的顺序被分解,first-term指定了身份模型中的一个用户或组,后续的术语的计算基于这个中间的用户或组。
privious的意思是任务被分配到当前已被鉴定的参与者,这意味着参与者完成了流程中的前一个步骤。
swimlane(swimlane-name)的意思是用户或组从指定的泳道实例中取得。
variable(variable-name)的意思是用户或组从指定变量实例中取得。变量实例可以包含一个java.lang.String,这种情况下,用户或组从身份组件获取;或者变量实例包含一个用户或组对象。
user(user-name)的意思是给定的用户从身份组件中取得。
group(group-name)的意思是给定的组从身份组件中取得。
11.11.2.2第二术语next-term
group(group-type)获取用户的组,这意味着前一个术语必须结果为一个用户,这个术语会使用给定的group-type在所有的membership中为用户搜索组。
member(role-name)从组里得到用户所履行的角色,前一个术语必须结果为一个组,这个术语在组中为用户搜索与给顶的role-name相匹配的membership。
11.11.3 移除身份组件
当你想要为组织信息使用自己的数据源时,例如你们公司的用户数据库或者ldap系统,你可以拔除身份组件,你唯一要做的就是在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继承并且重写getExpressiontSession方法。
protected ExpressionSession getExpressionSession(AssignmentContext assignmentContext);