下面开始接触
JBPM
的
JPDL
部分:
在
jpdl
参考手册中主要介绍了
processdefinition.xml
文件的格式(
schema
)
一、
Process archive:
如前面所描述的,
process archive
是商务流程的规则描述。它被打成
jar
包,通常以扩展名
.par
结束,
jbpm
识别一个流程需要三种类型的文件数据:
1、
业务流程的正式声明:在
jpdl
中,是以
processdefinition.xml
文件来表达。这一章节我们就来解析这个文件的格式。
2、
设计逻辑:在流程上加上规划逻辑这些也是在
processdefinition.xml
中给予描述的,在
process archive
中可以嵌套
java-classes
。所有在
process archive
中的
classes
应该放在子目录
/classes
中。
3、
其他资源文件。作为工作流引擎的客户端,在运行时你可能想要在流程中包含一些资源文件的变量。例如。窗体(
form
)的描述与某人执行任务相关联。
Jbpm
不能在你想要包含在一个流程定义中的资源文件的类型上做任何手脚。
二、
version
机制(此处
versioning
可以大致理解为:一个
process archive
部署后转变成过程定义的过程。)(此部分需要参考原文理解。感觉不对劲)
作为最最基本的,
jbpm
的翻译机制遵从下面原理:
l
每次
process archive
部署的时候,一个新的流程定义就在
jbpm
数据库中被创建。
l
在部署的时候,
jbpm
安排一个
version
号码(数字)给过程定义。只有当过程名与被定义的号相同时候才会执行
Process archives
。为了实现安排
version
号码,如果它是第一个
version jbpm
采取
1+
(the highest version number of the current process definitions with the same name)
或者
1
。从
jbpm-api
中可以通过一个给定的
name
查找最近的过程定义。
l
在一个给定的定义执行某一个过程实例,过程实例将会(等到同个定义结束前)一直在这个定义中保持执行状态。
l
通过这种方式能通过最近定义启动过程并且在相同定义中的完整的生命周期中保持运行状态。
l
注意:
jbpm
可以把一个设计的程序逻辑与一个过程相关联起来。通过在
process archive
中包含类文件,
jbpm
将会对于每个过程定义把
classes
分离出来。
三、
Processdefinition.xml
格式
文档类型定义:
<!DOCTYPE process-definition PUBLIC
"-//jBpm/jBpm Mapping DTD 2.0 beta3//EN"
"http://jbpm.org/dtd/processdefinition-2.0-beta3.dtd">
the document type definition of processdefinition.xml
过程定义:
<!ELEMENT process-definition ( description?,
swimlane*,
type*,
start-state,
( state |
milestone |
process-state |
decision |
fork |
join
)*,
end-state,
action* ) >
<!ATTLIST process-definition name CDATA #REQUIRED >
dtd fragment for process-definition
状态:
<!ELEMENT state ( description?, assignment?, action*, transition+ ) >
<!ATTLIST state name CDATA #REQUIRED >
dtd fragment for a state
在
JBPM
中,状态(
state
)这个术语与
FSM
(有限状态机)和
UML
状态图中有着同样的意思。
对商务流程进行建模的目的就是创建一个软件系统。我们考虑到过程定义是软件系统建立的一部分。所以
jbpm
中的状态术语是以一个软件系统的角度来解释的。
状态是
jbpm
的核心概念。在
jbpm
中当开始模仿一个流程,首要需要考虑的事情就是过程的状态。状态将成为你所设计的过程的基本框架。
以状态概念为核心的另外一个特殊原因是:状态在程序语言中没有容易重复混淆的概念。一个软件程序或者运行或者不运行。一个有代表性的商业过程都会与被分别执行的所设计的逻辑部分关联。
Jbpm
允许在相关联的程序逻辑中对状态建模。
Jpdl
也在状态之间定义了变迁,决定,分支,合并,
milestone
(
flow
)。需要注意的,控制流定义在状态与状态之间。无论什么情况下,一个正常的设计逻辑是更合适的,过程开发人员就能够在一个过程事件上指定一个
action
。
Jbpl
与其他过程定义语言如
bpel
等之间的区别之一是,
jpdl
具备以状态管理方式对
java
进行扩展和与程序语言最小交叠性的能力。
委派(
assignment
):
<ELEMENT assignment EMPTY >
<!ATTLIST assignment swimlane CDATA #IMPLIED
assignment (optional|required) #IMPLIED
authentication (optional|required|verify) #IMPLIED >
dtd fragment for an assignment
当一个流程执行到某种状态时候,工作流引擎将会等到一个外部的trigger(通过调用jbpm的api,ExecutionService.endOfState(…))。在这个方法里面,jbpm将会算出流程实例的下一个状态。
所以一个状态能够依赖于一个外部参与者。外部参与者可以是一个人也可以是某个系统。有许多种方式来实现业务流程中的状态与任务相关联。这些任务基本上可以分为两组:
1、
基于客户端的委派
在这种策略中,jbpm的用户管理他们自己用户的任务列表。在这种情况,jbpm只是用一个执行引擎来控管有限状态机。Jbpm的职责是计算并且追踪过程执行中的状态,然而客户端的职责就是确保每个人知道做了什么。然后客户端一般就是在一个给定的状态中找到过程实例(或者说tokens)
2、
基于流程的委派
(在jbpm2.0 beta3之前支持这种委派策略)
在这种委派策略中,jbpm将会担负给参与者安排状态并且追踪任务列表的职责。在jbpm中,流程在执行过程中是以一个token来进行跟踪的。一个token有一个指针来指向状态和一个参与者。在jbpm中一个参与者总是引用java.lant.string。为了详细说明jbpm如何必须安排tokens给参与者们,一些事件与此相关。接下来一个一个介绍:
只要一个客户端启动一个过程实例或者发出结束状态的信号,jbpm都会计算过程实例的下一个状态。
首要的事情是关于这些api方法中的第一个参数:参与者。这个参与用来识别谁来执行。
在启动一个新的过程实例的情况下,一个root-token在开始状态中被创建好。在结束一个状态的情况下,一个tokenid需要以一个某种状态中的参数形式指定。然后,jbpm将会开始为token计算下一个新状态。为了简单起见,我们忽略并发这种情况。Token将会经历变迁和节点,然后抵达下一个状态节点。在那时,一个token需要安排一个参与者。选择一个参与者之后,jbpm将通过token和调用的方法(开始过程实例或者结束状态)的返回值进行关联选择的参与者
所以当一个状态中的token具备有一个参与者时,那就意味着过程实例的执行是在等待这个参与者提供一个外部trigger给jbpm引擎。在这种情形下,过程中的状态对应一个用户的任务,jbpm通过检索所有已经安排给指定的参与者的tokens计算任务列表。现在,参与者能够从列表中选择一个token和发出结束状态信号量。
Assignmentn
(委派)有许多属性:assignment和authentication。Assingement属性可以有两个值:optional和required。Required意思指当执行到某一个状态时,jbpm将会检测是否把一个token安排了一个参与者。如果非法则会抛出AssignmentException的异常。如果assingment是optional(=default),当到达一个状态时jbpm就允许离开一个未安排的token。属性authentication指定约束条件来限定哪个参与者可以发出结束状态的信号。参与者通过endOfState方法中的参数actorid来指定。如果optional=default意思指对于指定谁来担当结束状态并不是必须的。Required意味着一个参与者需要被指定。Vertify意味着结束状态只可以安排给已经安排token的参与者。
更多信息请查看群组委派。查看faqs。
Swimlane
<!ELEMENT swimlane ( description?, delegation? ) >
<!ATTLIST swimlane name CDATA #REQUIRED >
dtd fragment for swimlane
非常典型的情况,一个人具备一个流程中多个状态的职责。在
jbpm
中通过创建一个
swimlane
并且通过
swimlane
安排状态给参与者(
actor
)。一个业务流程中的
swimlane
可以被看做为一个针对参与者的角色名字。
Jbpm
对
swimlane
的解释与
UML1.5
中的术语
swimlane
解释一致。首次执行到达一个状态,给定一个
swimlane
,就会算出参与者。
public interface AssignmentHandler {
String selectActor( AssignmentContext assignerContext );
}
the AssignmentHandler interface
在
swimalne
中的
delegation
标签指向一个
AssignmentHandler
的具体实现。
然后那个参与者(
actor
)以
swimlane
中同名的方式被存储为过程变量。下次当一个
process archives
在一个状态,利用
swimlane
,
jbpm
引擎将会检测变量并且安排
token
给参与者(存储在变量中的)
计算的结果是存储一个
swimlane
同名的过程变量。所以当下一个状态到达相同的
swimlane
时,状态就会安排给相同的
actor
,而不再用
AssignmentHandler
。因为在
swimlane
和参与者之间的关联已经存储在变量中,而且可以通过更新变量来改变
swimlane
参与者(
actor
)之间的关联关系。
变量和类型:
<!ELEMENT type ( description?, (delegation|transient), variable* ) >
<!ATTLIST type java-type CDATA #IMPLIED >
<!ELEMENT transient EMPTY >
<!ELEMENT variable EMPTY >
<!ATTLIST variable name CDATA #REQUIRED >
dtd fragment for type and variable
一个是变量是一种key-value对。它与过程实例(一次过程执行)相关联。Key是java.lang.string,value是任何java类型的任何pojo。所以任何是java类型,即使不给jbpm知道也能被应用到变量中。
变量存储过程实例的上下文信息(context)。变量可以通过下面几种方式进行设置读取:
-
ExecutionService.startProcessInstance( String actorId, Long definitionId, Map variables, String transitionName )
-
ExecutionService.endOfState( String actorId, Long tokenId, Map variables, String transitionName )
-
ExecutionService.setVariables( String actorId, Long tokenId, Map variables )
-
ExecutionContext.setVariable( String name, Object value )
-
ExecutionService.getVariables( String actorId, Long tokenId )
-
ExecutionContext.getVariable( String name )
当设计变量时候,jbpm尽量模仿java.util.map的语义。这一点可以通过jbpm-api来了解。也就是说一个变量只能当它被插入时被赋值,任何java类型都可以作为变量中的value。
一个type描述jbpm如何存储变量的值到数据库中。Jbpm有一个文本域用来存储值,中间以serializer来实现文本和对象之间的转换。
public interface Serializer {
String serialize( Object object );
Object deserialize( String text );
}
the Serializer interface
一个type 可以被看做一个serializer的引用。Jbpm包含了一些默认serializer的实现,其中包括下面java类型:
java.lang.String
java.lang.Long
java.lang.Double
by default supported java-types
变量类型匹配:
Java
类型声明的变量已经被默认支持,而不用在processdefinition.xml声明。Jbpm有个自动转换机制:当一个变量初始化后,jbpm按照下面步骤,首先检测java类型。如果它默认被java支持,那么就可以正常使用,否则,jbpm检查声明的类型是否与processdefinition.xml中java-type属性定义的变量是否一致。在匹配过程中jbpm也考虑变量值的父类。如果没有找不到这种类型,jbpm就把这样的变量作为transient(瞬间)变量对待。为了避免jbpm不得不执行匹配过程,你可以把变量指定到它各自类型中去。
Transient
(瞬间)变量:
一些变量并不需要做持久层处理存储在数据库中,jbpm支持瞬时变量。这样的变量不存储到数据库中并且只可以在jbpm-api的方法中使用。换句话说,瞬时变量的使用范围是:jbpm-api的方法。
开始状态:
<!ELEMENT start-state ( description?, transition+ ) >
<!ATTLIST start-state name CDATA #REQUIRED
swimlane CDATA #IMPLIED >
dtd fragment for start-state
开始状态是一个过程中的唯一状态,所有的过程实例从这里开始,注意在过程实例开始的时刻你能够把变量放在过程当中了。另外重点的概念是你能够有多个从开始状态出发的变迁,这样的话,就需要你指定哪个变迁是你启动过程实例时候需要执行的。
milestone
:
<!ELEMENT milestone ( description?, action*, transition ) >
<!ATTLIST milestone name CDATA #REQUIRED>
dtd fragment for a milestone
一个
milestone
是一种特殊的状态,它用来处理两个并发事件中的同步的作用。一个
milestone
可以被应用在下面情形:一条执行路径需要等待另外一个执行路径上的事件。如果
milestone
没有到达,接下来的执行就必须在
milestone
处等待,知道另外一个并发路径到达的时候,才可继续执行。如果
milestone
已经到达了(条件具备了),接下来的执行就是通过这个
milestone
状态。关于
milestone
的更多信息和图形请参看工作流模式。一个
milestone
与一或多个
actions
(用信号通知
milestones
到达)相关联。那些
actions
能够用默认
ActionHandler
:
org.jbpm.delegation.action.MilestoneReachdActionHandler
在流程中建模。所以
actions
(发信号给
jbpm
引擎告诉它一个
milestone
已经到达)能够像下面这样在
processdefition.xml
中调度:
...
<milestone name="theMilestone">
<transition to="stateAfterMilestone" />
</milestone>
...
<state name="stateBeforeReachingMilestone" swimlane="initiator">
<transition to="stateAfterReachingMilestone">
<action>
<delegation class="org.jbpm.delegation.action.MilestoneReachedActionHandler">theMilestone</delegation>
</action>
</transition>
</state>
...
modelling a milestone in the processdefinition.xml
process-state:
<!ELEMENT process-state ( description?, delegation, action*, transition+ ) >
<!ATTLIST process-state name CDATA #REQUIRED>
dtd fragment for a process-state
一个
process-state
符合父过程的
invocation
。父过程执行到一个
procee-state
时候就开始一个子过程的执行。过程残留在
process-state
中用以子过程的持续。当一个子过程完成后,就从一个
process-state
中离开了。
Decision:
<!ELEMENT decision ( description?, delegation, action*, transition+ ) >
<!ATTLIST decision name CDATA #REQUIRED>
dtd fragment for a decision
一个
decision
用以决定在多个执行路径中哪个才可以被执行。如果你是一个程序员,把它可以理解成
if-then=else
结构即可,当然。一个
decision
能够具有许多离开的变迁。
需要注意的是一个
decision
对某个情况建模,在这里工作流引擎根据上下文(
=variables
)和一些其他外部资源决定哪个路由可以被接受。作为可供选择的(可以替代的做法),你设计从一个状态出发具备多个变迁。在那种情况下,
jbpm
客户端必须通过调用以一个选择变迁名字作为一个参数的
endOfState
方法决定哪个变迁被执行。
Fork
(分支):
<!ELEMENT fork ( description?, delegation?, action*, transition+ ) >
<!ATTLIST fork name CDATA #REQUIRED
corresponding-join CDATA #IMPLIED>
dtd fragment for a fork
这个比较好理解,定义一般普通的
fork
行为一般是通过
ForkHandler
接口。但是默认行为(当在
fork
中没有
delegetion
时候)是
fork
的每个变迁都将获得一个子
token
。所以仅仅对一些高级外来的并发,你才需要实现
ForkHandler.
一般情况下,一个
fork
会有一个相关的
join
。这个
join
定义个并发的
block
。默认下
fork
和
join
仅支持严格的嵌套,并且默认情况不支持绕过并发
block
边界的变迁。
public interface ForkHandler {
void fork( ForkContext forkContext ) throws ExecutionException;
}
the ForkHandler interface
join
:
<!ELEMENT join ( description?, delegation?, action*, transition ) >
<!ATTLIST join name CDATA #REQUIRED
corresponding-fork CDATA #IMPLIED>
dtd fragment for a join
一个
fork joins
用于多条路径执行,用
JoinHandler
接口指定普通的
join
行为。但是默认情况下(没有
delegation
)是所有
fork
上的子
token
合并。最后
token
到达
join
将会触发父
token
最后引发
join
上的离开变迁。所以仅仅高级外来的并发你才需要实现
JoinHandler.
约束:一个
join
只能有一个离开变迁。
public interface JoinHandler {
void join( JoinContext joinContext ) throws ExecutionException;
}
the JoinHandler interface
结束状态:
<!ELEMENT end-state EMPTY >
<!ATTLIST end-state name CDATA #REQUIRED>
dtd fragment for end-state
一个过程定义有一个精确的结束状态。当一个过程实例执行到一个结束状态时候,这个过程实例就结束了。
变迁(
transition
):
<!ELEMENT transition ( action* )>
<!ATTLIST transition name CDATA #IMPLIED
to CDATA #REQUIRED>
dtd fragment for a transition
变迁用来指定节点之间的连接。变迁元素应该放在
node
里面,那么这个变迁就会从这个节点出离开。
Action
:
<!ELEMENT action ( delegation ) >
<!ATTLIST action event-type (process-start|process-end|
state-enter|state-leave|state-after-assignment|
milestone-enter|milestone-leave|
decision-enter|decision-leave|
fork-enter|fork-every-leave|
join-every-enter|join-leave|
transition) #IMPLIED>
dtd fragment for an action
一个
action
是一段
java
代码。在过程执行期间在一些事件基础之上
action
在工作流引擎上执行。
Action
总是被定义成一个过程定义元素(
process-definition, state, transition, decision, ...
)的子元素。当
action
执行的时候,父元素会在过程执行时候外加一些事件类型来定义精确时刻。可以想象得到,一个
action
的可能事件类型依赖于
action
被包含进去的元素。
事件类型名字(
event-type-names
)被暗示出是他们应用到的元素(理解有误)。
在
javadoc
中有事件类型,可以进行查看。
public interface ActionHandler {
void execute( ExecutionContext executionContext );
}
the ActionHandler interface
delegation
:
<!ELEMENT delegation ( #PCDATA ) >
<!ATTLIST delegation class CDATA #REQUIRED>
dtd fragment for a delegation
解释在约束元素和
delegation
类必须实现的接口之间的关联。
四、其他
process archive
文件
当一个
process archive
被部署,
processdefinition.xml
将被解析并且把信息存储到
jbpm
数据库中去。所有其他的文件或者被存储在数据库中或者文件系统,与被创建的过程定义相关联。作为一
jbpm api
的客户端你能够通过
ExecutionReadService.getFile( Long processDefinitionId, String fileName )
访问这些文件。
一个
process archive
和一个过程定义(
process definition
)之间的区别参看
versioning mechanism
。