第 6 章 jPDL
这章将会解释用来描述流程定义的 jPDL文件格式。jDPL是jBPM的突出的流程语言。jPDL的目标 是尽量精简和尽可能的开发者友好,在提供所有你期望 从BPM流程语言中获得功能的同时。
jPDL的schema文件包含了比这个文档中更多的属性和元素。 这个文档解释了jPDL中稳定的被支持的部分。 试验性的、不支持的jPDL特性可以在开发者指南中找到。
下面是一个jPDL流程文件的例子:
6.1.?process流程处理
顶级元素(element)是流程处理定义。
表?6.1.?process流程处理的属性
属性类型默认值是否必须描述name名称文本?必须在与用户交互时, 作为流程名字显示的一个名字或是标签。key键字母或数字,下划线如果省略,key中的非字母和非数字的字符会被替换为 下划线。可选(optional)用来辨别不同的流程定义。 拥有同一个key的流程会有多个版本。 对于所有已发布的流程版本,key-name这种组合都必须是 完全一样的。version版本整型比已部署的key相同的流程版本号高1, 如果还没有与之相同的key的流程被部署,那么版本就从1开始。可选流程的版本号表?6.2.?process流程的元素
元素个数描述description描述0个或1个描述文本activities活动至少1个流程中会有很多活动, 至少要有1个是启动的活动。
?6.2.?控制流程Activities活动
6.2.1.?start启动
说明一个流程的实例从哪里开始。 在一个流程里必须有一个开始节点。 一个流程必须至少拥有一个开始节点。 开始节点必须有一个向外的流向,这个流向会在流程启动的时候执行。
已知的限制:直到现在, 一个流程处理只能有一个启动节点(start)。
表?6.3.?start启动的属性
属性类型默认值是否必须描述name名称文本?可选活动的名字,在启动活动没有内部的转移(transition)时, name名称是可选的。表?6.4.?start启动的元素
元素个数描述transition转移1向外的转移
?6.2.2.?State状态节点
一个等待状态节点。 流程处理的流向会在外部触发器调用提供的API之前一直等待。 状态节点和其他的活动不一样, 它没有其他任何属性或元素。
6.2.2.1.?序列状态节点
让我们看一个用序列连接状态 和转移的例子。
图?6.1.?序列状态节点
下列代码将启动一个流向:
6.2.2.2.?可选择的状态节点
在第2个状态节点的例子里, 我们将演示如何使用状态节点实现 路径的选择。
图?6.2.?状态节点中的选择
让我们在这个流程处理定义里启动一个新的流程实例。
6.2.3.?decision决定节点
在多个选择中选择一条路径。也可以当做是一个决定。 一个决定活动拥有很多个向外的转移。当一个流向到达一个决定活动时, 会自动执行并决定交给哪个向外的转移。
一个决定节点应该配置成下面三个方式之一。
6.2.3.1.?decision决定条件
decision中会运行并判断每一个transition里的判断条件。 当遇到一个嵌套条件是true或者没有设置判断条件的转移, 那么转移就会被运行。
表?6.5.?exclusive.transition.condition 属性
属性类型默认值是否必须?描述exprexpression?required必须将被运行的 指定脚本langexpression language从脚本引擎配置里得到的默认代表性语言(default-expression-language)可选指定expr中执行的 脚本语言的种类例子:
图?6.3.?流程处理的决定条件例子
<condition expr="#{content=="good"}" /> </transition> <transition to="try again"> <condition expr="#{content=="not so good"}" /> </transition> <transition to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /></process>在使用
good content
启动一个流程之后submit document活动会变成活动的6.2.3.2.?decision expression唯一性表达式
decision表达式返回类型为字符串的 向外转移的名字。
表?6.6.?决定属性
属性类型默认值是否必须?描述exprexpression?required必须将被运行的指定 脚本langexpression language从脚本引擎配置里得到的默认指定的脚本语言(default-expression-language)可选指定expr中执行的脚本语言的 种类。例子:
图?6.4.?流程处理的决定表达式例子
expr="#{content}" > <transition name="good" to="submit document" /> <transition name="bad" to="try again" /> <transition name="ugly" to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /></process>当你使用good content启动一个新的流程实例,代码如下:
submit document活动。参考实例中的单元测试,获得其他场景。
6.2.3.3.?Decision handler决定处理器
唯一性管理是继承了DecisionHandler接口的java类。 决定处理器负责选择 向外转移。
handler的内容元素 可以在第?6.7?节 “用户代码”中找到。下面是一个决定使用DecisionHandler的流程处理例子:
图?6.5.?流程处理的exclusive管理例子
下面是ContentEvalation类:
6.2.4.?concurrency并发
流程的并发路径可以使用?
fork
?和?join
?活动来建模。下面的表格描述了?join
?的属性;fork
没有特别的属性。表?6.7.?
属性类型默认值是否必须?描述join
属性:multiplicity
整数或表达式传入转移的数目可选在这个join活动之前需要到达的执行的数目, 然后一个执行 会沿着join的单独的外向转移向外执行。lockmode
{none, read, upgrade, upgrade_nowait, write}upgrade可选hibernate的锁定模式,应用在上级执行, 来防止两个还没到达join的同步事务看到对方, 这会导致死锁。6.2.4.1.?使用?
fork
实现并行分支
fork
?活动允许将一个单独的流程路径分成 两个或多个分支,这些流程分支可以同步执行。图?6.6.?流程处理的并发例子
<fork name="fork"> <transition to="send invoice" /> <transition to="load truck"/> <transition to="print shipping documents" /> </fork> <state name="send invoice" > <transition to="final join" /> </state> <state name="load truck" > <transition to="shipping join" /> </state> <state name="print shipping documents"> <transition to="shipping join" /> </state> <join name="shipping join" > <transition to="drive truck to destination" /> </join> <state name="drive truck to destination" > <transition to="final join" /> </state> <join name="final join" > <transition to="end"/> </join> <end name="end" /></process>6.2.5.?end结束
结束流向
6.2.5.1.?end process instance结束流程处理实例
默认情况下,结束活动会终结已完成流程处理实例。 因此在流程处理实例中, 仍然在活动的多个并发(concurrent)流向(concurrent) 也会结束。
图?6.7.?结束活动
新的流程处理实例一创建便会直接结束。
6.2.5.2.?end execution结束流向
只有流向到达结束(end)活动时会结束流程处理实例, 并且其他并发流向会放弃活动。 我们可以设置属性ends="execution" 来达到这种状况。
表?6.8.?end execution属性
属性类型默认值是否必须描述ends{processinstance|execution}processinstanceoptional可选流向路径到达end活动 整个流程处理实例就会结束。
?6.2.5.3.?end multiple多个结束
一个流程处理可以有多个end events, 这样就很容易显示出流程处理实例的不同结果。示例:
图?6.8.?多个end events
如果你启动一个流向并使用下面的代码将它执行到get return code等待状态, 流向便会以bad request的end 活动(event)结束
6.2.5.4.?end State结束状态
流向(execution)可以以不同的状态结束。可以用其他的方式列出流程处理实例的结果。 可以用end event的状态属性或者end-cancel 和end-error表示。
表?6.9.?end execution 属性
属性类型默认值是否必须描述stateString?可选状态分配给流向参考下面流程的例子。
图?6.9.?不同的结束状态
这时,如果我们启动一个流向并使用下面的代码将流向执行到get return code等待状态, 流向会以取消状态(cancel state)结束。
和上面一样,使用值为200或500会让流向 分别以comleted或者error states结束。
6.2.6.?
task
在任务组件中,为一个人创建一个任务。
6.2.6.1.?
任务
分配者一个简单的任务会被分配给一个指定的用户
表?6.10.?
属性类型默认值是否必填描述任务
属性:assignee
表达式?可选用户id引用的用户 负责完成任务。图?6.10.?任务分配者示例流程
assignee="#{order.owner}"> <transition to="wait" /> </task> <state name="wait" /></process>这个流程演示了任务分配的两个方面。第一,?
assignee
用来指示用户, 负责完成任务的人。分配人是一个任务中的字符串属性 引用一个用户。第二,这个属性默认会当做表达式来执行。 在这里任务被分配给
#{order.owner}
。 这意味着首先使用order这个名字查找一个对象。 其中一个查找对象的地方是这个任务对应的流程变量。 然后getOwner()
方法会用来 获得用户id, 引用的用户负责完成这个任务。这就是我们例子中使用到得Order类:
johndoe的任务列表可以像下面这样获得。assignee="johndoe"。 在这里,任务会被分配给johndoe。6.2.6.2.?
task
候选人任务可能被分配给一组用户。 其中的一个用户应该接受这个任务并完成它。
表?6.11.?
属性类型默认值是否必填描述任务
属性:candidate-groups
表达式?可选一个使用逗号分隔的组id列表。 所有组内的用户将会成为这个任务的 候选人。candidate-users
表达式?可选一个使用逗号分隔的用户id列表。 所有的用户将会成为这个任务的候选人。图?6.11.?任务候选人示例流程
这是一个使用任务候选人的示例流程:
candidate-groups="sales-dept"> <transition to="wait" /> </task> <state name="wait"/></process>在启动之后,一个任务会被创建。这个任务不显示在任何人的个人任务列表中。 下面的任务列表会是空的。
sales-dept组成员的 分组任务列表中。在我们的例子中,
sales-dept
有两个成员:johndoe和joesmoecandidate-users属性 可以用来处理用逗号分隔的一系列用户id。?candidate-users
属性可以和其他分配选项结合使用。6.2.6.3.?
任务
分配处理器一个
AssignmentHandler
可以通过编程方式来计算 一个任务的分配人和候选人。AssignmentHandler extends Serializable { /** sets the actorId and candidates for the given assignable. */ void assign(Assignable assignable, OpenExecution execution) throws Exception;}
Assignable
是任务和泳道的通用接口。 所以任务分配处理器可以使用在任务, 也可以用在泳道中(参考后面的内容)。
assignment-handler
是任务元素的一个子元素。 它指定用户代码对象。所以assignment-handler
的属性和元素 都来自第?6.7?节 “用户代码”让我们看一下任务分配的例子流程。
图?6.12.?任务分配处理器的示例流程
<assignment-handler /> </field> </assignment-handler> <transition to="wait" /> </task> <state name="wait" g="255,16,88,52" /></process>引用的类
AssignTask
看起来像这样:TaskAssignmentHandler的新流程实例 会立即让新流程实例运行到任务节点。 一个新review
任务被创建,在这个时候?AssignTask
的分配处理器被调用。这将设置johndoe
为分配人。 所以John Doe将在他自己的任务列表中找到这个任务。6.2.6.4.?
任务
泳道一个流程中的多任务应该被分配给同一个用户或换选人。 一个流程中的多任务可以分配给一个单独的泳道。 流程实例将记得换选人和用户,在泳道中执行的第一个任务。 任务序列在同一个泳道中将被分配给 这些用户和候选人。
一个泳道也可以当做一个流程规则。 在一些情况下, 这可能与身份组件中的权限角色相同。 但是实际上它们并不是同一个东西。
表?6.12.?
属性类型默认值是否必填描述任务
属性:swimlane
泳道(字符串)?可选引用一个定义在流程中的泳道泳道可以被声明在流程元素中:
表?6.13.?
属性类型默认值是否必填描述泳道
属性:name
泳道(字符串)?必填泳道名称。 这个名称将被任务泳道属性中引用。assignee
表达式?可选用户id引用的用户 负责完成这个任务。candidate-groups
表达式?可选一个使用逗号分隔的组id列表。 所有组中的人将作为这个任务的这个泳道中的 候选人。candidate-users
表达式?可选一个使用逗号分隔的用户id列表。 所有的用户将作为这个任务的这个泳道中的 候选人。图?6.13.?任务泳道示例流程
任务泳道示例是下面这个流程文件:
<swimlane name="sales representative" candidate-groups="sales-dept" /> <start> <transition to="enter order data" /> </start> <task name="enter order data" swimlane="sales representative"> <transition to="calculate quote"/> </task> <task name="calculate quote" swimlane="sales representative"> </task></process>在这个例子中,我们在身份组件中 创建了下面的信息:
johndoe将成为?enter order data
的一个候选人。还是像上一个流程候选人例子一样, John Doe可以像这样接收任务:johndoe成为任务的分配人。 直到任务与泳道sales representative
关联, 分配人johndoe
也会关联到泳道中 作为分配人。接下来,John Doe可以像下面这样完成任务:
calculate quote。 这个任务也关联着泳道。因此, 任务会分配给johndoe
。 初始化分配的候选人用户和候选人组也会从泳道复制给任务。 这里所指的用户johndoe
?会释放任务,返回它给其他候选人。6.2.6.5.?
任务
变量任务可以读取,更新流程变量。 稍后任务可以选择定义任务本地流程变量。 任务变量是任务表单的一个很重要的部分。 任务表单显示来自任务和流程实例的数据。 然后从用户一侧录入的数据会转换成设置的任务变量。
获得任务变量就像这样:
6.2.6.6.?在任务中支持e-mail
可以为分配人提供一个提醒, 当一个任务添加到他们的列表时,以及在特定的时间间隔进行提醒。 每个email信息都是根据一个模板生成出来的。模板可以在内部指定, 或者在配置文件中的?
process-engine-context
部分指定。表?6.14.?
元素数目描述notification0..1让一个任务被分配的时候发送一个提醒消息。 如果没有引用模板,也没有提供内部的模板, mail会使用task-notification名字的模板。reminder0..1根据指定的时间间隔发送提醒信息。 如果没有引用模板,也没有提供内部模板, mail会使用task-reminder名字的模板。task
元素表?6.15.?
属性类型默认值是否必填描述notification
属性continue
{sync | async | exclusive}sync可选指定在发送提醒邮件后, 是不是产生一个异步执行。表?6.16.?
reminder
属性: