摘要:在工作流引擎的设计过程中,如何确保引擎兼具内核的轻巧性和外延的强大性,是一直困扰我们的关键设计问题。本文在系统地研究jBPM的建模语言和体系结构的基础上,提炼出jBPM针对上述问题的解决方案:
¨ 在流程静态建模层面,建模语言jPDL系统地、全面地支持自定义构件的挂接;适度分离粗略的业务流程定义和精确的技术流程定义,并确保软件二次开发人员可以在业务分析人员给出的业务流程模型的基础上进行精化,保持技术流程基于业务流程的延续性。
¨ 在流程动态执行层面,从流程执行支撑、流程功能增强两个方面系统地、全面地设置可拔插(pluggable)的扩充点:
ü 流程执行支撑扩充点包括:流程定义适配,组织模型及任务分派适配,流程实例存储服务适配,外部应用系统适配。
ü 流程功能增强扩充点包括:任务策略扩充,事件监听扩充,日程调度扩充,工作日历扩充,安全控制扩充。
1 流程静态建模机制的扩充性
在jBPM的流程定义语言jPDL中,定义了多种扩充点,包括:条件路由,业务逻辑扩展,任务分派。
1.1条件路由
条件路由通过建模语言中的decision节点来实现。有两种路由方法,一种在静态建模时定义路由条件,另一种由代理类的Java代码在运行时动态决定路由。
¨ 静态路由:在decision节点中说明Beanshell表达式。Beanshell是jBPM采用的一种script语言。
例1-1:
<decision name='decision'>
<transition name='important lead' to='harras them'>
<condition expression = ‘……’ />
</ transition>
<transition name='lead' to='put it in the lead db'>
<condition expression = ‘……’ />
</ transition>
<transition name='beggars' to='forget about it'>
<condition expression = ‘……’ />
</ transition>
</decision>
¨ 动态路由:在decision节点中说明代理(delegation)Java类,由该类在流程实例运行过程中决定路由。
例1-2:
<decision name='decision'>
<handler class='org.jbpm.jpdl.exe.DecisionHandlerTest'/>
<transition name='important lead' to='harras them' />
<transition name='lead' to='put it in the lead db'/>
<transition name='beggars' to='forget about it'/>
</decision>
1.2业务逻辑扩展
jBPM的建模语言jPDL主要通过其event机制提供业务逻辑扩充点。jPDL有预定义的event类型,主要包括:节点进入(node-enter)、节点离开(node-leave)、流程启动(process-start)、流程结束(process-end)、任务创建(task-create)、 任务分派(task-assign)、任务启动(task-start)、定时器事件等。二次开发人员也可以自定义event类型。每个event都可以与action列表绑定。action是一个Java类,其中可以描述定制的业务逻辑。在流程实例运行过程中,当系统捕获到event时,执行相应的action列表。
jPDL提供的业务逻辑扩充点还不止于上述event机制。在node和transition中,均可以定义action,通过action可以对应二次开发人员定制的Java类,从而实现基本工作流系统的业务逻辑扩充。
1.3 任务分派
¨ 在流程建模时静态地定义任务分派目标:
ü 针对任务(task)的分派:
例1-3:
<task name="evaluate web order">
<assignment expression="user(ernie)" />
</swimlane>
ü 针对泳道(swimlane)的分派:
例1-4:
<swimlane name="salesman">
<assignment expression="group(sale)" />
</swimlane>
说明:“泳道”是指,在一个工作流模型中执行者相同的任务构成的集合。
¨ 通过扩充点动态地定义任务分派目标:
例1-5:
<task name='change nappy'
<assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler'
</task>
上例中,实际的任务分派动作由用户自定义类NappyAssignmentHandler完成。该类必须实现jBPM的标准接口AssignmentHandler。
1.4 异常处理
在流程定义(process-definition)、节点(node)和迁移(transition)中,均可以定义异常处理器(exception-handler)。异常处理器的内容为一系列的actions,每个action再对应二次开发人员定义的Java类。
2 流程动态执行机制的扩充点
2.1 流程执行支撑扩充点
2.1.1组织模型及任务分派适配
因为组织模型与任务分派紧密相关,所以工作流引擎必须考虑组织模型;但是,组织模型与应用环境也是紧密关联的,所以很难给出标准的、可复用的组织模型。为此,jBPM采用以下递进策略:
¨ jBPM利用其identity compoment来提供对最简单的组织模型的支持,该组织模型仅描述用户、组以及它们之间的关系,其类图如下:
上图中,User代表用户或服务,Group代表一类User。Group可以有不同的类型。Membership描述了User和Group之间的多对多关系,也可以用来指示User在Group中扮演的角色。
为了描述基于上述简单组织模型的任务分派,jBPM定义了一种任务分派表达式,用于在流程建模时静态地定义任务执行者。任务分派表达式的语法如下:
<assignment-exp> ::= <first-term> [--> next-term] 0..*
<first-term> ::= previous |
swimlane(swimlane-name) |
variable(variable-name) |
user(user-name) |
group(group-name)
<next-term> ::= group(group-type) |
member(role-name)
例如,previous --> group(‘hierarchy’) --> member(‘boss’) 表示:前一任务执行者所在的类型为hierarchy的组中角色为boss的员工。
¨ 在实际应用时,用户的组织机构几乎肯定会比jBPM identity compoment提供的组织模型复杂。所以,二次开发人员可以将实际的组织模型映射到上述简单模型。映射的方法是:填充jBPM identity compoment规定的三张数据库表格:User,Group和Membership。
¨ 如果二次开发人员觉得实现上述映射有困难,那么可以完全抛开identity compoment,转而采用任务分派的代理(delegation)机制,即,提供一个任务分派代理类,在该类的Java代码中实现任务分派。jBPM要求代理类必须实现org.jbpm.taskmgmt.def.AssignmentHandler接口。见例1-5。
2.1.2流程实例存储服务适配
流程实例存储服务是指为工作流引擎在流程实例运行过程中提供信息保存和读取的服务机制。为了实现DBMS无关性,jBPM采用以下递进策略:
¨ 缺省情况下,jBPM采用Hibernate实现流程实例存储服务。二次开发人员可以通过修改Hibernate的映射和配置文件,就可以根据需要适应特殊的数据库需求。
¨ jBPM3.1在存储服务机制中引进了新的扩充点:二次开发人员可以在代码中指定JDBC connection、Hibernate session和Hibernate session factory。一旦通过以下持久服务资源注入方法来设置自定义的connection、session(factory),jBPM会用它们替代上述配置文件中的资源。
ü JbpmContext.setConnection(java.sql.Connection connection)
ü JbpmContext.setSession(org.hibernate.Session
session)
ü JbpmContext.setSessionFactory(org.hibernate.SessionFactory sessionFactory
);
2.1.3外部应用系统适配
工作流系统与外部应用的挂接通过建模语言中的action机制来实现。Action可以定义在流程(process)中、节点(node)中、迁移(transition)中、事件(event)中,以及定时器(timer)中。在action的定义中可以指定自定义的Java类,在此Java类中调用外部应用的功能。
例2-1:
<process-definition name="Hi Process">
<start-state name="start">
<event type="node-leave">
<action name="Hi action" class="com.aaa.HiActionHandler"
config-type="bean">
<city>Shanghai</city>
<rounds>2</rounds>
</action>
</event>
<transition name="tr" to="next_state"></transition>
</start-state>
……
</process-definition>
在上例中,流程实例运行时一旦发生从节点“start”出发的迁移,嵌于事件中的action将被执行,从而实现对类com.aaa.HiActionHandler中Java代码的调用。在该类的代码中,二次开发人员可以实现与外部应用的互操作。
jBPM的建模语言允许通过XML配置向action中的自定义类传递初始参数,例如例2-1中的参数“city”和“rounds”。在action自定义类中,也可以通过jBPM API实现对工作流变元的读写。
2.2 流程功能增强扩充点
2.2.1 任务策略扩充
jBPM在任务策略方面的扩充点包括:任务分配、任务执行与提交时任务与流程之间的信息传递。
¨ 任务分配扩充点:“1.3 任务分派”已经描述了任务分派策略的扩充点,即,可以在流程建模时给出基于identity compoment简单组织模型的表达式,静态地定义任务分派目标;也可以指定任务分派代理类,由该代理类在流程实例运行过程中动态确定任务分派目标。除上述扩充点外,jBPM还支持两种任务分配模式:push模式和pull模式。二次开发人员可以根据需要选择自己的分配模式。
ü 在push模式下,工作流引擎(或者其任务分派模块)直接将任务实例与具体的actorId相关联,引擎自动将任务实例push到actor的任务列表中。如果前述分派表达式的计值结果为单个actorId,或者二次开发人员指定任务分派代理类使用Assignable.setActorId(String actorId),那么jBPM系统采用push模式完成任务分配。否则,如果前述分派表达式的计值结果为多个actorId构成的集合,或者二次开发人员指定任务分派代理类使用Assignable. setPooledActors(String[] pooledActors),那么jBPM系统采用下述的pull模式完成任务分配。
ü 在pull模式下,工作流引擎(或者其任务分派模块)将任务实例与多个actorId相关联,这些actorId构成的集合称为pooledActors。jBPM使该任务实例对pool中所有actor可见,但是引擎并不将任务实例推送到这些actor的任务列表中,而是等待pool中的某个actor将该任务实例pull进自己的任务列表中。一旦任务实例被pool中某个actor pull,引擎将该任务实例设置成对pool中其他actor不可见。
¨ 任务与流程实例信息交流扩充点:在任务执行时,可能需要读、写流程变量;在任务完成并提交时,可能需要写流程变量。为此,jBPM提供了“任务变量”的概念。在某些情况下,任务变量和流程变量并非简单的一一对应关系,例如,三个流程变量代表三个月的销售额,任务变量只需要它们的平均值。为实现任务与流程实例之间的信息交流,jBPM设置了任务控制器机制。该机制也采用递进模式:首先,jBPM提供基本(默认)的任务控制器;如果不敷使用,二次开发人员可以使用自定义的任务控制器。jBPM的任务控制器机制在流程变量和任务变量之间架起了一座桥梁。
ü 使用jBPM默认的任务控制器:
<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>
上例中,任务控制器将流程变量a、b、c依次映射为任务变元x、y、c;x从a中读取初值,但在任务完成、提交时x的新值并不写回至a;y从b中读取初值,并在任务完成、提交时将y的新值写回至b;至于c,在任务执行时直接使用流程变量c,并在任务完成、提交时将c的新值带回至流程实例执行环境。
ü 使用自定义的任务控制器:
<task name="clean ceiling">
<controller class="com.yourcom.CleanCeilingTaskControllerHandler">
-- here goes your task controller handler configuration --
</controller>
</task>
二次开发人员提供的自定义任务控制器必须实现以下接口:
public interface TaskControllerHandler extends Serializable
{
List getTaskFormParameters(TaskInstance taskInstance);
void submitParameters(Map parameters, TaskInstance taskInstance);
}
其中,getTaskFormParameters函数负责从流程实例运行环境读取数据至任务执行环境;submitParameters则负责将任务执行环境中的数据提交至流程实例运行环境。
2.2.2 事件监听扩充
jBPM的事件由工作流模型图形元素(node、transition、superstate和process-definition等)和事件类型唯一确定。jBPM定义了一系列与工作流模型图形元素相关联的事件,例如,流程实例运行过程中,可以触发节点进入(node-enter)、节点离开(node-leave)、流程启动(process-start)、流程结束(process-end)、任务创建(task-create)、 任务分派(task-assign)、任务启动(task-start)等事件。
在静态建模时,jBPM的事件均与actions绑定。事件的触发将导致相应actions的执行。如“1.2业务逻辑扩展”所述,这是jBPM支持业务逻辑扩展的主要手段。
<process-definition>
<event type="node-enter">
<script>
System.out.println("this script is entering node "+node);
</script>
</event>
...
</process-definition>
jBPM的事件传播采用自底向上的模式,关联于某个图形元素的事件首先发往该图形元素;在执行完该图形元素中针对该类事件指定的actions之后,事件继续传向该图形元素的父元素。jBPM对图形元素之间的父子关系定义如下:超状态(superstate)是包含于超状态中的图形元素(例如node,transition等)的父元素;process-definition是所有顶级图形元素(不位于任何超状态中)的父元素。
二次开发人员可以自定义事件类型,并通过GraphElement.fireEvent(String eventType, ExecutionContext executionContext)来发送事件。自定义事件遵循与jBPM标准事件相同的事件处理逻辑和事件传播逻辑。
<process-definition>
<event type="custom-event">
<action name="custom-action" class=" com.aaa.CustomEventHandler "/>
</event>
...
</process-definition>
2.2.3 日程调度扩充
jBPM通过定时器(timer)实现日程调度。在node中加入timer元素,即可实现基于定时器的节点执行监控。
<state name='catch crooks'>
<timer name='reminder' duedate='3 business hours' repeat='10 business minutes'
transition='time-out-transition' >
<action class='the-remainder-action-class-name' />
</timer>
...
</state>
在上例中,一旦流程实例运行进入state 'catch crooks',定时器reminder即被创建。该定时器在3个工作小时到期,到期后马上执行action类中的Java代码,然后实施time-out-transition迁移。
通过在事件的action中加入create-timer和cancel-timer动作,可以分别实现事件对定时器的创建和取消。
2.2.4 工作日历扩充
jBPM的工作日历提供业务时间信息,并可用于计算任务和定时器的超时时间。jBPM缺省提供的属性文件jbpm.business.calendar.properties 定义了工作日历的配置信息,例如,上、下班时间,节假日,日期/时间格式等。
二次开发人员可以通过修改该属性文件来实现对工作日历的定制。
2.2.5 安全控制扩充
安全控制机制包括用户认证和授权控制。jBPM3.0尚未实现完整的缺省安全控制机制,但是其框架为未来的安全控制机制提供了扩充点。
¨ 用户认证。jBPM认为其工作流引擎一定会运行于特定容器,例如WebApp,EJB,SWING等,所以它认为引擎的运行环境必须实现用户认证功能,而jBPM引擎只需获取认证的结果(即通过认证的actorId)即可。jBPM通过接口Authenticator中的getAuthenticatedActorId()函数获取通过认证的actorId。二次开发人员可以在配置文件中指定自定义的Authenticator接口的实现类。
¨ 授权控制。jBPM引擎通过接口Authorizer中的checkPermission(Permission permission, ProcessDefinition processDefinition, Token token)判别用户是否具有相关操作的权限。jBPM3.0定义了6种操作权限:创建流程实例权限(CreateProcessInstancePermission),部署流程实例权限(DeployProcessPermission),任务分派权限(TaskAssignPermission)、结束任务权限(EndTaskPermission)、查看任务参数权限(ViewTaskParametersPermission)、提交任务参数权限(SubmitTaskParametersPermission)。类似于前述的用户认证扩充点,二次开发人员可以在配置文件中指定自定义的Authorizer接口的实现类,从而实现符合特定用户需求的授权控制机制。