第1章 介绍
JBOSS jBPM是一个灵活的、可扩展的工作流管理系统。JBOSS jBPM拥有直观的流程语言,用任务、异步的等待状态、定时器、自动化动作…等来表示业务流程图,把这些操作绑定在一起,JBOSS jBPM就拥有了非常强大和可扩展的控制流机制。
JBOSS jBPM只有最小的依赖,可以象使用java库一样非常容易的使用它。另外,也可以通过把它部署在J2EE集群应用服务器中,用在吞吐量极为关键的环境中。
JBOSS jBPM可被配置为任何数据库,并且可以部署到任何应用服务器。
1.1 概述
核心工作流和BPM功能被打包为一个简单的java库,这个库包括一个存储到数据库、从数据库更新和获取流程信息的服务。
图 1.1 JBOSS jBPM组件概观
1.2 JBOSS jBPM入门套件
入门套件是一个包含了所有jBPM组件的下载包,包括:
l Jbpm-server,一个预配置好的jboss应用服务器。
l Jbpm-designer,图形化设计jBPM流程的eclipse插件。
l Jbpm-db,jBPM数据库兼容包(见下文)。
l Jbpm,核心jbpm组件,包括库和本指南。
l Jbpm-bpel,JBOSS jBPM BPEL扩展参考。
预配置好的JBOSS应用服务器安装了下列组件:
l 核心的jBPM组件,被打包为了一个服务档案。
l 一个包括jBPM库表的集成数据库:默认的hypersonic数据库包含了jBPM表,另外还包含一个流程。
l jBPM控制台web应用程序,可以由流程参与者使用,也可以由jBPM管理员使用。
l jBPM调度程序,用于定时器执行。调度程序在入门套件中被配置为一个servlet,这个servlet将产生一个线程来监视和执行定时器。
l jBPM命令执行器,用于命令的异步执行。命令执行器也被配置为一个servlet,这个servlet将产生一个线程来监视和执行命令。
l 一个流程实例,已经被部署到了jBPM数据库中。
1.3 JBOSS jBPM图形化流程设计器
JBOSS jBPM还包括一个图形化设计工具,这个设计器是一个创作业务流程的图形化工具。
JBOSS jBPM图形化流程设计器是一个eclipse插件,可以独立安装的设计器已经在开发目标中。
图形化设计器非常重要的特性是它同时支持业务分析者和技术开发者,这使的业务流程建模可以平滑的转换到实际实现。
插件可以被用作本地更新设置(普通的zip文件),通过标准的eclipse软件更新机制安装。另外还有一个包,你可以直接把它解压到eclipse主目录里。
1.4 JBOSS jBPM核心组件
JBOSS jBPM核心组件是普通java(J2SE)软件,用来管理流程定义和流程实例执行的运行时环境。
JBOSS jBPM是一个java库,因此,它可以被用在任何java环境,例如web应用、swing应用、EJB、webservice…等,jBPM库还可以被打包为无状态会话EJB,这允许被作为集群部署,并且适用于极高的吞吐量。无状态会话EJB按照J2EE1.3规范编写,因此它可以部署到任何应用服务器。
JBOSS jBPM核心组件被打包为一个简单的java库文件,依赖于你所使用的功能,jbpm-3.0.jar库对第三方库有些依赖,如hibernate、dom4j和其他,这些在“第5章 部署”TODO中有清晰的说明。
为了持久化,jBPM内部使用hibernate,除了传统的O/R影射之外,hibernate还解决了不同数据库之间的SQL方言(dialect)问题,使jBPM可以方便的在当前所有数据库上移植。
JBOSS jPBM API可以从你的项目中任何定制的java软件中访问,例如你的web应用、你的EJB、你的webservice组件、你的消息驱动bean,或者其他java组件。
1.5 JBOSS jBPM控制台web应用程序
jBPM控制台web应用程序服务于两个目的。首先,它作为与由流程执行所产生的运行时任务相交互的一个重要的用户接口;其次,它是一个管理和监控控制台,允许检查和操纵运行时实例。
1.6 JBOSS jBPM身份组件
JBOSS jBPM可以与任何包含用户或其他组织信息目录的公司集成,但是对于没有组织信息组件可用的项目,JBOSS jBPM包含了它自己的组件。身份组件所使用的模型比传统的servlet、ejb和portlet(译者注:portlet是portal中最重要的组件,与servlet类似,portlet是部署在容器中用来生成动态内容的web组件。)模型更丰富。
更多信息,请看“11.11 身份组件”。TODO
1.7 JBOSS jBPM调度程序
JBOSS jBPM调度程序是一个用来监视和执行定时器的组件,它在流程执行期间被调度。
定时器组件软件被打包进核心的jbpm库,但是它需要被部署进下列环境之一:要么配置调度程序servlet去产生监视线程,要么用调度程序的main方法启动一个独立的JVM。
1.8 JBOSS jBPM数据库兼容包
JBOSS jBPM数据库兼容包是一个包含能使jBPM在你所选择的数据库上运行的所有信息、驱动程序和脚本的下载包。
1.9 JBOSS jBPM BPEL扩展
JBOSS jBPM BPEL 扩展是一个独立的扩展包,它扩展了jBPM,使之支持 BPEL(Business Process Execution Language商业流程执行语言),BPEL本质上是一个xml脚本语言,用来根据其他web服务(web services)编写web 服务(web services)。
第3章 指南
这个指南将向你展示如何用jpdl创建基本的流程以及如何使用API管理运行期的执行。
这个指南的形式是解释一组示例,每个示例集中于一个特殊的主题,并且包含大量的注释,这些例子也可以在jBPM下载包的目录src/java.examples中找到。
最好的学习方法就是建立一个工程,并且通过在给定例子上做不同的变化进行实验。
对eclipse用户来说可以如下方式开始:下载jbpm-3.0-[version].zip并且解压到自己的系统,然后执行菜单“File”-->“Import…”-->“Existing Project into Workspace”,然后点击“Next”,浏览找到jBPM根目录,点击“Finish”。现在,在你的工作区中就有了一个jbpm.3工程,你可以在src/java.examples/…下找到本指南中的例子,当你打开这些例子时,你可以使用菜单“Run”-->“Run As…”-->“JUnit Test”运行它们。
jBPM包含一个用来创作例子中展示的XML的图形化设计器工具,你可以在“2.1 下载概述”中找到这个工具的下载说明,但是完成本指南不需要图形化设计器工具。
3.1 Hello World 示例
一个流程定义就是一个有向图,它由节点和转换组成。Hello world流程有三个节点,下面来看一下它们是怎样组装在一起的,我们以一个简单的流程作为开始,不用使用设计器工具,下图展示了hello world流程的图形化表示:
图 3.1 hello world流程图
public void testHelloWorldProcess() {
// 这个方法展示了一个流程定义以及流程定义的执行。
// 这个流程定义有3个节点:一个没有命名的开始状态,
// 一个状态“s”,和一个名称为“end”的结束状态。
// 下面这行是解析一段xml文本到ProcessDefinition对象(流程定义)。
// ProcessDefinition把一个流程的规格化描述表现为java对象。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 下面这行是创建一个流程定义的执行。创建后,流程执行有一个
// 主执行路径(=根令牌),它定位在开始状态。
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
// 创建后,流程执行有一个主执行路径(=根令牌)。
Token token = processInstance.getRootToken();
// 创建后,主执行路径被定位在流程定义的开始状态。
assertSame(processDefinition.getStartState(), token.getNode());
// 让我们开始流程执行,通过它的默认转换离开开始状态。
token.signal();
// signal方法将会把流程阻塞在一个等待状态。
// 流程执行进入第一个等待状态“s”,因此主执行路径现在定位
// 在状态“s”。
assertSame(processDefinition.getNode("s"), token.getNode());
// 让我们发送另外一个信号,这将通过使用状态“s”的默认转换
// 离开状态“s”,恢复流程执行。
token.signal();
// 现在signal方法将返回,因为流程示例已经到达结束状态。
assertSame(processDefinition.getNode("end"), token.getNode());
}
3.2 数据库示例
jBPM的特性之一就是在流程等待状态时,拥有把流程的执行持久化到数据库中的能力。下面的例子将向你展示怎样存储一个流程实例到数据库,例子中还会出现上下文。分开的方法被用来创建不同的用户代码,例如,一段代码在web应用中启动一个流程并且持久化执行到数据库,稍后,由一个消息驱动bean从数据库中加载流程实例并且恢复它的执行。
有关jBPM持久化的更多信息可以在“第7章 持久化”找到。
public class HelloWorldDbTest extends TestCase {
static JbpmConfiguration jbpmConfiguration = null;
static {
// 在“src/config.files”可以找到象下面这样的一个示例配置文件。
// 典型情况下,配置信息在资源文件“jbpm.cfg.xml”中,但是在这里
// 我们通过XML字符串传入配置信息。
// 首先我们创建一个静态的JbpmConfiguration。一个JbpmConfiguration
// 可以被系统中所有线程所使用,这也是为什么我们可以把它安全的设置
// 为静态的原因。
jbpmConfiguration = JbpmConfiguration.parseXmlString(
"<jbpm-configuration>" +
//jbpm-context机制分离了jbpm核心引擎和来自于外部环境的服务。
" <jbpm-context>" +
" <service name='persistence' " +
" factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
" </jbpm-context>" +
// 同样,jbpm使用的所有资源文件在jbpm.cfg.xml中被提供。
" <string name='resource.hibernate.cfg.xml' " +
" value='hibernate.cfg.xml' />" +
" <string name='resource.business.calendar' " +
" value='org/jbpm/calendar/jbpm.business.calendar.properties' />" +
" <string name='resource.default.modules' " +
" value='org/jbpm/graph/def/jbpm.default.modules.properties' />" +
" <string name='resource.converter' " +
" value='org/jbpm/db/hibernate/jbpm.converter.properties' />" +
" <string name='resource.action.types' " +
" value='org/jbpm/graph/action/action.types.xml' />" +
" <string name='resource.node.types' " +
" value='org/jbpm/graph/node/node.types.xml' />" +
" <string name='resource.varmapping' " +
" value='org/jbpm/context/exe/jbpm.varmapping.xml' />" +
"</jbpm-configuration>"
);
}
public void setUp() {
jbpmConfiguration.createSchema();
}
public void tearDown() {
jbpmConfiguration.dropSchema();
}
public void testSimplePersistence() {
// 在下面调用的3个方法之间,所有的数据通过数据库被传递。
// 在这个测试中,这3个方法被依次执行,因为我们想要测试一个
// 完整的流程情景。但是实际上,这些方法表示了对服务器的不同
// 请求。
// 因为我们以一个干净的空数据库开始,所以我们首先必须部署流程。
// 事实上,这只需要由流程开发者做一次。
deployProcessDefinition();
// 假设在一个web应用中当用户提交一个表单时我们起动一个流程
// 实例(=流程执行)…
processInstanceIsCreatedWhenUserSubmitsWebappForm();
// 然后,一个异步消息到达时继续执行。
theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();
}
public void deployProcessDefinition() {
// 这个测试展示了一个流程定义以及流程定义的执行。
// 这个流程定义有3个节点:一个没有命名的开始状态,
// 一个状态“s”,和一个名称为“end”的结束状态。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition name='hello world'>" +
" <start-state name='start'>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 查找在上面所配置的pojo持久化上下文创建器。
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
// 部署流程定义到数据库中。
jbpmContext.deployProcessDefinition(processDefinition);
} finally {
// 关闭pojo持久化上下文。这包含激发(flush)SQL语句把流程
// 定义插入到数据库。
jbpmContext.close();
}
}
public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
// 本方法中的代码可以被放在struts的actiong中,或JSF管理
//的bean中。
//查找在上面所配置的pojo持久化上下文创建器。
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
GraphSession graphSession = jbpmContext.getGraphSession();
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("hello world");
// 使用从数据库中获取的流程定义可以创建一个流程定义的执行
// 就象在hello world例子中那样(该例没有持久化)。
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
Token token = processInstance.getRootToken();
assertEquals("start", token.getNode().getName());
// 让我们起动流程执行
token.signal();
// 现在流程在状态 's'。
assertEquals("s", token.getNode().getName());
// 现在流程实例processInstance被存储到数据库,
// 因此流程执行的当前状态也被存储到数据库。
jbpmContext.save(processInstance);
// 以后我们可以从数据库再取回流程实例,并且通过提供另外一个
// 信号来恢复流程执行。
} finally {
// 关闭pojo持久化上下文。
jbpmContext.close();
}
}
public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
// 本方法中的代码可以作为消息驱动bean的内容。
// 查找在上面所配置的pojo持久化上下文创建器。
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
GraphSession graphSession = jbpmContext.getGraphSession();
// 首先,我们需要从数据库中取回流程实例。
// 有几个可选方法来分辨出我们在这里所要处理的流程实例。
// 在这个简单的测试中,最容易的方式是查找整个流程实例列表,
// 这里它应该只会给我们一个结果。
// 首先,让我们查找流程定义。
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("hello world");
// 现在我们搜索这个流程定义的所有流程实例。
List processInstances =
graphSession.findProcessInstances(processDefinition.getId());
// 因为我们知道在这个单元测试中只有一个执行。
// 在实际情况中, 可以从所到达的信息内容中提取processInstanceId
// 或者由用户来做选择。
ProcessInstance processInstance =
(ProcessInstance) processInstances.get(0);
// 现在我们可以继续执行。注意:processInstance 将委托信号
// 到主执行路径(=根令牌)。
processInstance.signal();
// 在这个信号之后,我们知道流程执行应该到达了结束状态。
assertTrue(processInstance.hasEnded());
// 现在我们可以更新数据库中的执行状态。
jbpmContext.save(processInstance);
} finally {
// 关闭pojo持久化上下文。
jbpmContext.close();
}
}
}
3.3 上下文示例:流程变量
流程变量包含了流程执行期间的上下文信息,流程变量与一个java.util.Map相似,它影射变量名称和值,值是java对象,流程变量作为流程实例的一部分被持久化。为了让事情简单,在这里的例子中我们只是展示使用变量的API,而没有持久化。
有关变量的更多信息可以在“第10章 上下文”中找到。
// 这个例子仍然从hello world流程开始,甚至没有修改。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
// 从流程实例获取上下文实例,用来使用流程变量。
ContextInstance contextInstance =
processInstance.getContextInstance();
// 在流程离开开始状态之前,我们要在流程实例的上下文中
// 设置一些流程变量。
contextInstance.setVariable("amount", new Integer(500));
contextInstance.setVariable("reason", "i met my deadline");
// 从现在开始,这些流程变量与流程实例相关联。现在展示由用户代码通过
// API访问流程变量,另外,这些代码也可以存在于动作或节点的实现中。
// 流程变量被作为流程实例的一部分也被存储到数据库中。
processInstance.signal();
// 通过contextInstance访问流程变量。
assertEquals(new Integer(500),
contextInstance.getVariable("amount"));
assertEquals("i met my deadline",
contextInstance.getVariable("reason"));
3.4 任务分配示例
下一个例子我们将向你展示怎样分配一个任务到用户。因为jBPM工作流引擎与组织模型是独立的,所以任何一种用来计算参与者的表达式语言都是有限制的,因此,你不得不指定一个AssignmentHandler实现,用来包含任务参与者的计算。
public void testTaskAssignment() {
// 下面的流程基于hello world 流程。状态节点被一个task-node节点
// 所替换。task-node JPDL中的一类节点,它表示一个等待状态并且产生
// 将要完成的任务,这些任务在流程继续之前被执行。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition name='the baby process'>" +
" <start-state>" +
" <transition name='baby cries' to='t' />" +
" </start-state>" +
" <task-node name='t'>" +
" <task name='change nappy'>" +
" <assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' />" +
" </task>" +
" <transition to='end' />" +
" </task-node>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 创建一个流程定义的执行。
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
Token token = processInstance.getRootToken();
// 让我们起动流程执行,通过默认转换离开开始状态
token.signal();
// signal方法将会把流程阻塞在一个等待状态,
// 在这里,就是阻塞在task-node。
assertSame(processDefinition.getNode("t"), token.getNode());
// 当执行到达task-node,一个'change nappy'任务被创建,并且
// NappyAssignmentHandler 被调用,来决定任务将要分配给谁。
// NappyAssignmentHandler 返回'papa'。
// 在一个实际环境中,将使用org.jbpm.db.TaskMgmtSession中的方法
// 从数据库获取任务。因为我们不想在这个例子中包含复杂的持久化,
// 所以我们仅使用这个流程实例的第一个任务实例(我们知道,在这个
// 测试情景,只有一个任务)。
TaskInstance taskInstance = (TaskInstance)
processInstance
.getTaskMgmtInstance()
.getTaskInstances()
.iterator().next();
// 现在我们检查taskInstance 是否真正的分配给了'papa'。
assertEquals("papa", taskInstance.getActorId() );
// 现在我们假设'papa'已经完成了他的职责,并且标示任务为已完成。
taskInstance.end();
// 因为这是要做的最后一个任务(也是唯一一个),所以任务的完成
// 会触发流程实例的继续执行。
assertSame(processDefinition.getNode("end"), token.getNode());
}
3.5 定制动作示例
动作是一种绑定你自己的定制代码到jBPM流程的机制。动作可以与它自己的节点(如果它们与流程的图形化表示是相关的)相关联。动作也可以被放置在事件上,如执行转换、离开节点或者进入节点;如果那样的话,动作则不是图形化表示的一部分,但是在流程执行运行时,当执行触发事件时,它们会被执行。
我们先看一下将要在我们的例子中使用的动作实现:MyActionHandler,这个动作处理实现实际上没有做任何事…仅仅是设置布尔变量isExecuted为true。变量isExecuted是一个静态变量,因此它可以在动作处理的内部访问(即内部方法中),也可以从动作(即在动作类上)验证它的值。
有关动作的更多信息可以在“9.5 动作”中找到。
// MyActionHandler 是一个在jBPM流程执行期间可以执行用户代码的类。
public class MyActionHandler implements ActionHandler {
// 在每个测试之前(在 setUp方法中), isExecuted 成员被设置为false。
public static boolean isExecuted = false;
// 动作将设置isExecuted为true,当动作被执行之后,单元测试会
// 展示。
public void execute(ExecutionContext executionContext) {
isExecuted = true;
}
}
就象前面所提示那样,在每个测试之前,我们将设置静态域MyActionHandler.isExecuted为false。
// 每个测试都将以设置MyActionHandler的静态成员isExecuted
// 为false开始。
public void setUp() {
MyActionHandler.isExecuted = false;
}
我们将会在转换上开始一个动作。
public void testTransitionAction() {
// 下面的流程与hello world 流程不同。我们在从状态“s”到
// 结束状态的转换上增加了一个动作。这个测试的目的就是展示
// 集成java代码到一个jBPM流程是多么的容易。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </transition>" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
// 让我们为流程定义起动一个新的执行。
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
// 下面的信号会导致执行离开开始状态,进入状态“s”。
processInstance.signal();
// 这里我们展示MyActionHandler还没有被执行。
assertFalse(MyActionHandler.isExecuted);
// ... 并且执行的主路径被定位在状态“s”。
assertSame(processDefinition.getNode("s"),
processInstance.getRootToken().getNode());
// 下面的信号将触发根令牌的执行,令牌将会执行带有动作的转换,
// 并且在调用signal方法期间动作经会被执行token。
processInstance.signal();
// 我们可以看到MyActionHandler在调用signal方法期间被执行了。
assertTrue(MyActionHandler.isExecuted);
}
下一个例子展示了相同的动作,但是现在动作分别被放置在了enter-node和leave-node事件上。注意,节点与转换相比有更多的事件类型,而转换只有一个,因此动作要放置在节点上应该放入一个event元素中。
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <event type='node-enter'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </event>" +
" <event type='node-leave'>" +
" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
" </event>" +
" <transition to='end'/>" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
assertFalse(MyActionHandler.isExecuted);
//下面的信号会导致执行离开开始状态,进入状态“s”,
// 因此状态 's' 被进入,动作被执行。
processInstance.signal();
assertTrue(MyActionHandler.isExecuted);
// 我们重新设置MyActionHandler.isExecuted。
MyActionHandler.isExecuted = false;
// 下一个信号将会触发执行离开状态's',因此动作将被执行。
processInstance.signal();
// 请看
assertTrue(MyActionHandler.isExecuted);
第5章 部署
jBPM是一个嵌入式BPM引擎,这意味着你可以象安装一个独立的软件产品并集成一样把jBPM嵌入到你自己的java工程中,可以这样做的一个主要方面就是最小化的依赖,本章讨论jbpm库及其依赖。
5.1 Java运行环境
jBPM3要求J2SE 1.4.2 +
5.2 jBPM库
jbpm-[version].jar是核心功能库。
jbpm-identity-[version].jar是包含在“11.11 身份组件”中描述的身份组件的库(可选的)。
5.3 第三方库
在一个最小化的部署中,你仅仅通过放置commons-logging和dom4j库到你的classpath,就可以使用jBPM创建和运行流程,但是这样不支持流程的持久化。如果你不使用流程的xml解析,可以移除dom4j库,改为编程创建对象图。
表格 5.1
库 | 用途 | 描述 | 目录 |
commons-logging.jar | 在jBPM和hibernate中记录日至。 | jBPM代码日志记录到commons logging,commons logging库可以被配置为分发日志到java1.4日志、log4j、…等等,有关怎样配置commons logging的更多信息请看apache commons 用户指南。如果你使用log4j,最简单的方式就是把log4j库和一个log4j.properties放置到classpath,commons logging将会自动检测并使用该配置。 | lib/jboss(从jboss 4.0.3 ) |
Dom4j- 1.6.1 .jar | 流程定义和hibernate持久化。 | Xml解析。 | lib/dom4j |
jBPM的典型部署中将包括流程定义和流程执行的持久化,在这种情况下,jBPM除了对hibernate及其所依赖库之外不再有任何其他依赖。
当然,hibernate所需要的库依赖于环境以及你将使用的特性,详细信息请查询hibernate文档。下表给出了一个普通POJO部署环境下的指示。
jBPM的发布使用hibernate3.1,但是它也可以使用3.0.x,如果那样的话,你不得不在hibernate.queries.hbm.xml配置文件中更新一些hibernate查询,有关自定义查询的更多信息,请看“7.6自定义查询”。
表格 5.2
库 | 用途 | 描述 | 目录 |
hibernate3.jar | hibernate持久化。 | 最好的O/R映射器。 | lib/hibernate(hibernate3.1) |
antlr- 2.7.5 H3.jar | 由hibernate持久化的查询分析使用。 | 解析库。 | lib/jboss(从jboss 4.0.3 ) |
cglib-2.1_2jboss.jar | hibernate持久化。 | hibernate代理所使用的反射库。 | lib/jboss(从jboss 4.0.3 ) |
Commons-collection.jar | hibernate持久化。 |
| lib/jboss(从jboss 4.0.3 ) |
ehcache-1.1.jar | hibernate持久化(默认配置)。 | 二级缓存实现,当为hibernate配置不同的的缓存时,不需要本库。 | lib/hibernate |
jaxen-1.1-beta-4.jar | 流程定义和hiberante持久化。 | XPath库(由dom4j使用)。 | lib/hibernate |
jdbc2_0-stdext.jar | hibernate持久化。 |
| lib/hibernate |
asm.jar | hibernate持久化。 | asm字节码库。 | lib/hibernate |
asm-attrs.jar | hibernate持久化。 | asm字节码库。 | lib/hibernate |
beanshell库是可选的,如果你不包含它,你将不能把beanshell集成到jbpm流程语言中使用,并且你将会得到一个日志信息说“jbpm不能加载Script类,因此script元素不能使用”。
表格 5.3
库 | 用途 | 描述 | 目录 |
bsh- 1.3.0 .1.jar | beanshell脚本解释程序。 | 只用在script和decision元素中,当你不使用这些流程元素时,可以移除beanshell库,但是必须在hibernate.cfg.xml文件中注释掉Sceipt.hbm.xml映射行。 | lib/jboss |
第6章 配置
jBPM配置由java类org.jbpm.JbpmConfiguration来描述,获取JbpmConfiguration的最简单方式是使用单态实例方法JbpmConfiguration.getInstance()。
如果你想从另外一个源加载配置,你可以使用JbpmConfiguration.parseXxxx方法。
static JbpmConfinguration jbpmConfiguration = JbpmConfinguration.getInstance();
JbpmConfiguration是线程安全的,因此可以在一个静态成员中维护,所有的线程都可以把JbpmConfiguration作为一个JbpmContext对象的工厂来使用。JbpmContext表示一个事务,在一个上下文块中JbpmContext使服务可用,上下文块如下:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
// This is what we call a context block.
// Here you can perform workflow operations
} finally {
jbpmContext.close();
}
JbpmContext使一组服务和配置为jBPM可用,这些服务在jbpm.cfg.xml配置文件中被配置,并且使jBPM无论运行在任何java环境中这些服务都可用。
下面是JbpmContext的一个典型配置,就象你在src/config.files/jbpm.cfg.xml中看到的那样:
<jbpm-configuration>
<jbpm-context>
<service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
<service name='message' factory='org.jbpm.msg.db.DbMessageServiceFactory' />
<service name='scheduler' factory='org.jbpm.scheduler.db.DbSchedulerServiceFactory' />
<service name='logging' factory='org.jbpm.logging.db.DbLoggingServiceFactory' />
<service name='authentication' factory='org.jbpm.security.authentication.DefaultAuthenticationServiceFactory' />
</jbpm-context>
<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name='resource.hibernate.cfg.xml' value='hibernate.cfg.xml' />
<!-- <string name='resource.hibernate.properties' value='hibernate.properties' /> -->
<string name='resource.business.calendar' value='org/jbpm/calendar/jbpm.business.calendar.properties' />
<string name='resource.default.modules' value='org/jbpm/graph/def/jbpm.default.modules.properties' />
<string name='resource.converter' value='org/jbpm/db/hibernate/jbpm.converter.properties' />
<string name='resource.action.types' value='org/jbpm/graph/action/action.types.xml' />
<string name='resource.node.types' value='org/jbpm/graph/node/node.types.xml' />
<string name='resource.parsers' value='org/jbpm/jpdl/par/jbpm.parsers.xml' />
<string name='resource.varmapping' value='org/jbpm/context/exe/jbpm.varmapping.xml' />
<int name='jbpm.byte.block.size' value="1024" singleton="true" />
<bean name='jbpm.task.instance.factory' class='org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl' singleton='true' />
<bean name='jbpm.variable.resolver' class='org.jbpm.jpdl.el.impl.JbpmVariableResolver' singleton='true' />
<long name='jbpm.msg.wait.timout' value='5000' singleton='true' />
</jbpm-configuration>
在这个配置中你可以看到三部分:
l 第一部分使用一组服务实现配置jbpm上下文,这些配置的可选项在以后描述特定服务实现的章节中做了描述。
l 第二部分是所有配置资源的引用映射,如果你想要定制某些配置文件,这些资源引用可以被修改。典型情况下,你可以在jbpm-3.x.jar中拷贝一个默认的配置,并且把它放在classpath中的某个位置,然后在这个文件中修改引用为你自己定制版本的配置文件。
l 第三部分是在jbpm中使用的一些别名配置,这些配置选项在包含特定主题的章节中做了描述。
缺省配置的一组服务被定位于一个简单的web应用环境和最小的依赖,持久化服务将获得一个jdbc连接,所有其他服务将会使用这个相同的连接来完成它们的服务,因此,工作流的所有操作都被集中到一个JDBC连接的一个事务中,不再需要事务管理器。
JbpmContext包含为大多流程操作所提供的方便方法:
public void deployProcessDefinition(ProcessDefinition processDefinition) {...}
public List getTaskList() {...}
public List getTaskList(String actorId) {...}
public List getGroupTaskList(List actorIds) {...}
public TaskInstance loadTaskInstance(long taskInstanceId) {...}
public TaskInstance loadTaskInstanceForUpdate(long taskInstanceId) {...}
public Token loadToken(long tokenId) {...}
public Token loadTokenForUpdate(long tokenId) {...}
public ProcessInstance loadProcessInstance(long processInstanceId) {...}
public ProcessInstance loadProcessInstanceForUpdate(long processInstanceId) {...}
public ProcessInstance newProcessInstance(String processDefinitionName) {...}
public void save(ProcessInstance processInstance) {...}
public void save(Token token) {...}
public void save(TaskInstance taskInstance) {...}
public void setRollbackOnly() {...}
记住,XxxForUpdate方法将记录所加载的对象为自动保存的,因此不需要再调用save方法。
指定多个jbpm上下文是可能的,但是你必须确保每个jbpm上下文的name属性必须是唯一的,可以使用JbpmConfiguration.createContext(String name)获取命名的上下文。
service元素定义了一个服务名称和此服务的服务工厂,服务将只会在使用JbpmContext.getServices().getService(String name)请求时被创建。
工厂也可以被作为一个元素来指定,而不是用属性。那样可以用于在工厂对象中注入一些配置信息,负责解析XML的组件创建和装配被调用的对象工厂。
6.1 配置属性
jbpm.byte.block.size:文件附件和二进制变量被存储到数据库,不是作为blob,而是作为一个固定大小的二进制对象列表,这可以方便用于不同数据库并且提高jBPM的全面嵌入能力,这个参数控制了固定长度块的大小。
jbpm.task.instance.factory:定制任务实例的创建方式,在这个属性中指定一个全类名,当你想要定制TaskInstance bean并且向它添加新的属性时,这是有必要的。请参考“11.10 定制任务实例”,指定的类必须实现org.jbpm.tskmgmt.TaskInstanceFactory。
jbpm.variable.resolver:定制jBPM在JSF表达式中寻找第一个术语的方式。
jbpm.msg.wait.timout:定制消息缓存的时间。
6.2 配置文件
下面是对在jBPM中定义的所有配置文件的简短描述。
6.2.1 Hibernate.cfg.xml文件
这个文件包含hibernate配置,并且引用hibernate映射资源文件。
位置:hibernate.cfg.xml文件如果不另外在jbpm.properties文件的jbpm.hibernate.cfg.xml属性中指定,则jBPM工程中的默认hibernate配置文件在目录src/config.files/hibernate.cfg.xml。
6.2.2 Hibernate查询配置文件
这个文件包含jBPM会话org.jbpm.db.*Session中所使用的hibernate查询。
位置:org/jbpm/db/hibernate.queries.hbm.xml。
6.2.3 节点类型配置文件
这个文件包含了XML节点元素到节点实现类的映射。
位置:org/jbpm/graph/node/node.types.xml。
6.2.4 动作类型配置文件
这个文件包含了XML动作元素到动作实现类的映射。
位置:org/jbpm/graph/action/action.types.xml。
6.2.5 业务日历配置文件
包含了业务时间和空闲时间的定义。
位置:org/jbpm/calendar/jbpm.business.calendar.properties。
6.2.6 变量映射配置文件
指定了流程变量(java对象)的值怎样转换到用于存储到jbpm数据库中的变量实例。
位置:org/jbpm/context/exe/jbpm.varmapping.xml。
6.2.7 转换器配置文件
指定了id到类名的映射。id被存储到数据库,org.jbpm.db.hibernate.ConverterEnumType被用来映射id到单态对象。
位置:org/jbpm/db/hibernate/jbpm.converter.properties。
6.2.8 缺省模块配置文件
指定哪个模块被缺省添加到一个新的流程定义ProcessDefinition。
位置:org/jbpm/graph/def/jbpm.default.modules.properties。
6.2.9 流程档案解析器配置文件
指定流程档案解析的解析器。
位置:org/jbpm/jpdl/par/jbpm.parsers.xml。
6.3 对象工厂
对象工厂可以依照bean的xml配置文件创建对象,配置文件指定了对象将被怎样创建、配置以及组装到一起形成一个完整的对象图。对象工厂可以注入配置和其他bean到一个bean中。
在最简单的形式中,对象工厂可以从这样一个配置中创建基本类型和java bean:
<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance"/>
<string name="greeting">hello world</string>
<int name="answer">42</int>
<boolean name="javaisold">true</boolean>
<float name="percentage">10.2</float>
<double name="salary">100000000.32</double>
<char name="java">j</char>
<null name="dusttodust" />
</beans>
---------------------------------------------------------
ObjectFactory of = ObjectFactory.parseXmlFromAbove();
assertEquals(TaskInstance.class, of.getNewObject("task").getClass());
assertEquals("hello world", of.getNewObject("greeting"));
assertEquals(new Integer(42), of.getNewObject("answer"));
assertEquals(Boolean.TRUE, of.getNewObject("javaisold"));
assertEquals(new Float(10.2), of.getNewObject("percentage"));
assertEquals(new Double(100000000.32), of.getNewObject("salary"));
assertEquals(new Character('j'), of.getNewObject("java"));
assertNull(of.getNewObject("dusttodust"));
你也可以配置列表:
<beans>
<list name="numbers">
<string>one</string>
<string>two</string>
<string>three</string>
</list>
</beans>
以及map:
<beans>
<map name="numbers">
<entry><key><int>1</int></key><value><string>one</string></value></entry>
<entry><key><int>2</int></key><value><string>two</string></value></entry>
<entry><key><int>3</int></key><value><string>three</string></value></entry>
</map>
</beans>
Bean可以使用直接成员注入和通过属性的setter。
<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<field name="name"><string>do dishes</string></field>
<property name="actorId"><string>theotherguy</string></property>
</bean>
</beans>
Bean可以被引用,被引用的对象不必必须是一个bean,也可以是一个字符串、整型或其他任何对象。
<beans>
<bean name="a" class="org.jbpm.A" />
<ref name="b" bean="a" />
</beans>
Bean可以使用任何构造器构造(构造函数)。
<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor>
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
…或者在bean上使用工厂方法…
<beans>
<bean name="taskFactory"
class="org.jbpm.UnexistingTaskInstanceFactory"
singleton="true"/>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor factory="taskFactory" method="createTask" >
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
…或者在类上使用一个静态工厂方法…
<beans>
<bean name="task" class="org.jbpm.taskmgmt.exe.TaskInstance" >
<constructor factory-class="org.jbpm.UnexistingTaskInstanceFactory" method="createTask" >
<parameter class="java.lang.String">
<string>do dishes</string>
</parameter>
<parameter class="java.lang.String">
<string>theotherguy</string>
</parameter>
</constructor>
</bean>
</beans>
每个命名的对象都可以使用属性singleton=“true”标记为单态,这意味着给定的对象工厂为每个请求将总是返回相同的对象。注意,单态不能在不同对象工厂之间共享。
单态特性导致getObject和getNewObject方法间的区别,对象工厂的典型用户将使用getNewObject,这意味着在一个新的对象图被构造之前对象对象工厂首先要清除对象缓存。在构造对象图期间,非单态对象被存储在对象工厂的对象缓存中,允许共享引用一个对象。单态对象缓存不同于普通对象缓存,单态对象缓存不需要清除,而普通对象缓存在每个getNewObject方法被调用起始会被清除。 第7章 持久化
在很多情况下,Jbpm需要维护跨越长时间的流程的执行,在这里,“长时间”意味着跨越几个处理事务。因为流程执行就像是状态机,在一个处理事务中,我们就是把流程执行状态机从一个状态转到下一个状态,所以持久化的主要目的就是在等待状态存储流程的执行。
一个流程定义可以表现为三种不同形式:XML、Java对象、Jbpm数据库中记录。执行(运行时)信息和日志信息可以表现为两种形式:Java对象和Jbpm数据库中的记录。
图 7.1 转换和不同形式
有关XML表示流程定义以及流程档案的更多信息,请参考“第16章Jbpm流程定义语言(JPDL)”。
关于怎样部署流程档案到数据库在“第 16.1.1 节部署流程档案” 中可以找到。
7.1 持久化API
7.1.1 相关配置框架
持久化API集成在了配置框架中,通过JbpmContext暴露出了一些便利方法,因此持久化API要像下面这样在JbpmContext块中使用:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
// Invoke persistence operations here
} finally {
jbpmContext.close();
}
在下文中,我们假设配置中包含如下持久化服务(如示例配置文件src/config.files/jbpm.cfg.xml):
<jbpm-configuration>
<jbpm-context>
<service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
...
</jbpm-context>
...
</jbpm-configuration>
7.1.2 JbpmContext中的便利方法
三个普通的持久化操作是:
l 部署流程
l 启动一个流程的执行
l 继续一个执行
首先部署一个流程定义。典型的,这可以从图形化流程设计器或部署流程的Ant任务直接完成,但是在这里你会看到怎样通过编程实现:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
ProcessDefinition processDefinition = ...;
jbpmContext.deployProcessDefinition(processDefinition);
} finally {
jbpmContext.close();
}
要创建一个新的流程执行,我们需要指定一个要实例化执行的流程定义,通常的作法是指定一个流程名称,并让Jbpm在数据库中查找这个流程的最新版本:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
String processName = ...;
ProcessInstance processInstance =
jbpmContext.newProcessInstance(processName);
} finally {
jbpmContext.close();
}
要继续一个流程执行,我们需要从数据库中取出这个流程实例、令牌或者任务实例,在这些POJO Jbpm对象上调用一些方法,然后再把对流程实例的更新保存到数据库中。
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
long processInstanceId = ...;
ProcessInstance processInstance =
jbpmContext.loadProcessInstance(processInstanceId);
processInstance.signal();
jbpmContext.save(processInstance);
} finally {
jbpmContext.close();
}
注意:如果你在JbpmContext中使用xxxForUpdate方法,则jbpmContext.save不需要再显式的调用,因为在jbpmContext关闭时它会被自动调用。举例来说,假设我们要通知Jbpm一个任务实例已经完成,(注意:任务实例完成能够触发继续执行,所以相联的流程实例必须保存。)一个非常方便的方法是使用loadTaskInstanceForUpdate方法:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
long taskInstanceId = ...;
TaskInstance taskInstance =
jbpmContext.loadTaskInstanceForUpdate(taskInstanceId);
taskInstance.end();
} finally {
jbpmContext.close();
}
正象是后台信息一样,下面部分解释了Jbpm是怎样管理持久化和使用Hibernate的。
JbpmConfiguration维护了一组服务工厂(ServiceFactory),服务工厂象前面内容所说的在jbpm.cfg.xml中配置,并且是lazy(懒)实例化的。当在第一次需要DbpersistenceServiceFactory时它被实例化,然后服务工厂就被在JbpmConfiguration中维护,DbpersistenceServiceFactory管理一个hibernate SessionFactory,而hibernate SessionFactory也是在第一次被请求的时候才创建的。
图 7.2 持久化相关类
在调用jbpmConfiguration.createJbpmContext()方法期间,只有JbpmContext被创建,这时没有更多的持久化相关实例。JbpmContext管理一个DbPersistenceService,它在被第一次请求的时候实例化。DbPersistenceServece管理hibernate会话(session),hibernate会话在DbPersistenceServece中也是被Lazy(懒)创建的,因此结果是:一个hibernate会话只有在第一个请求持久化的操作被调用时才被打开,而在这之前不会。
7.1.3 高级API用法
DbPersistenceService维护了一个懒实例化的hibernate 会话,所有的数据库存取都是通过这个hibernate会话完成的。所有的查询和更新都是通过Jbpm暴露出的xxxSession类完成的,例如GraphSession、SchedulerSession、LoggingSession等等,这些session类都是提交hibernate查询并且都使用下层相同的hebernate会话。
XxxxSession类也是通过JbpmContext来获取的。
7.2 配置持久化服务
7.2.1 The hibernate session factory
默认情况下,DbpersistenceServiceFactory将使用classpath根下的资源文件hibernate.cfg.xml来创建hibernate会话工厂。注意:hibernate资源配置文件由“jbpm.hibernate.cfg.xml”属性指定,可以在jbpm.cfg.xml中定制。下面是默认配置:
<jbpm-configuration>
...
<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name='resource.hibernate.cfg.xml'
value='hibernate.cfg.xml' />
<!-- <string name='resource.hibernate.properties'
value='hibernate.properties' /> -->
...
</jbpm-configuration>
当属性“resource.hibernate.properties”被指定,则指定的资源文件中的属性将会覆盖hibernate.cfg.xml中的配置,因此我们可以使用hibernate.properties文件、而不是更新hibernate.cfg.xml文件来方便的更新Jbpm指向自己的数据库:hibernate.cfg.xml仅仅需要拷贝过去不需要做任何更改。
7.2.2 The DbPersistenceServiceFactory
DbpersistencrServiceFactory拥有三个配置属性:isTransactionEnabled、sessionFactoryJndiName和dataSourceJndiName。要指定这几个属性中的任何一个,你需要象bean一样在factory元素中定义服务工厂,如下:
<jbpm-context>
<service name="persistence">
<factory>
<bean factory="org.jbpm.persistence.db.DbPersistenceServiceFactory">
<field name="isTransactionEnabled"><false /></field>
<field name="sessionFactoryJndiName">
<string value="java:/myHibSessFactJndiName" />
</field>
<field name="dataSourceJndiName">
<string value="java:/myDataSourceJndiName" />
</field>
</bean>
</factory>
</service>
...
</jbpm-context>
l isTransactionEnabled:默认情况下,Jbpm将会开始并且结束hibernate事务。如果禁用事务并且禁止使用hibernate管理事务,可以象上例一样配置isTransactionEnabled属性为false。更多信息,请参考“第7.3节 hibernate事务” 。
l sessionFactoryJndiName:默认情况下这个属性为空,意味着会话工厂不从JNDI获取。如果设置了,当需要使用会话工厂创建一个hibernate会话时,则将会从所提供的JNDI名称中通过jndi获取会话工厂。
l dataSourceJndiName:默认情况下这个属性为空,JDBC连接的创建会委托给hibernate。通过指定一个datasource,当打开一个新的会话时,Jbpm将会从datasource中获取一个JDBC连接并且把这个连接提供给hibernate。有关用户提供JDBC连接,请参考“第7.5节用户提供的素材”。
7.3 Hibernate事务
默认情况下,Jbpm将委托事务到hibernate,并且使用每个事务一个会话的模式。Jbpm在hibernate会话被打开时启动一个hibernate事务,这在第一次调用jbpmContext的一个持久化操作时发生,事务会在hiberante会话关闭之前被提交,这在jbpmContext.close()中发生。
使用jbpmContext.setRollbackOnly()回滚一个事务,在这种情况下,事务会在jbpmContext.close()方法中关闭会话之前回滚。
要禁止Jbpm在hibernate API之上调用任何事务,可以设置isTransactionEnabled属性为false,就象上面的“第 7.2.2 节 The DbPersistenceServiceFactory”说明的一样。
7.4 管理事务
当在J2EE应用服务器中(如JBOSS)使用Jbpm时,大多情况下需要管理事务。如:
l 在应用服务器中配置一个DataSource
l 配置hibernate使它的连接使用DataSource
l 使用容器管理事务
l 在Jbpm中禁用事务
7.5 用户提供的素材
你也可以编程向Jbpm提供JDBC连接、hibernate会话和hibernate会话工厂。
当这些资源提供给Jbpm时,Jbpm会使用所提供的资源,而不是配置中指定的。
JbpmContext类包含了一些方便的方法来实现通过编程注入资源。例如,向Jbpm提供一个JDBC连接,可以使用如下代码:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
Connection connection = ...;
jbpmContext.setConnection(connection);
// invoke one or more persistence operations
} finally {
jbpmContext.close();
}
JbpmContext类有如下方便的方法用来支持编程提供资源:
JbpmContext.setConnection(Connection);
JbpmContext.setSession(Session);
JbpmContext.setSessionFactory(SessionFactory);
7.6 自定义查询
Jbpm使用的所有HQL查询都集中在一个配置文件中,这个资源文件在hibernate.cfg.xml配置文件中被引用,如下:
<hibernate-configuration>
...
<!-- hql queries and type defs -->
<mapping resource="org/jbpm/db/hibernate.queries.hbm.xml" />
...
</hibernate-configuration>
要自定义一个或更多查询,先拷贝一个原始文件并且把你自定义的版本放在classpath下的某个位置,然后在hibernate.cfg.xml中更新 “org/jbpm/db/hibernate.queries.hbm.xml”,使之指向你自己定义的版本。
7.7 数据库兼容
Jbpm可以在hibernate所支持的任何数据库上运行。
Jbpm中的示例配置文件(src/config.files)指定了使用的是hypersonicin in-memory数据库,这个数据库在开发和测试阶段是一个理想的选择,hypersonicin in-memory数据库把数据放在内存中而不是存储到磁盘。
7.7.1 改变Jbpm数据库
下面列出了当改变Jbpm使用一个不同数据库时的作法:
l 把jdbc驱动库放在classpath
l 改变Jbpm所使用的hibernate配置
l 在新的数据库中创建表
7.7.2 Jbpm数据库表
Jbpm.db子项目包含了一系列的驱动库、说明和脚本来帮助你在你所选择的数据库上开始工作。请查阅jbpm.dm工程根目录下的readme.html文件来获取更多信息。
尽管Jbpm有能力生成所有数据库的DDL脚本,但是这些不总是最优的,因此你可能想要你的DBA查阅这些生成的DDL,来进行列类型的优化和使用索引。
在开发中,你可能对下面的hibernate配置感兴趣:如果你设置hibernate配置属性“hibernate.hbm2ddl.auto”为“create-drop”(例如在hibernate.cfg.xml中),则在应用中第一次使用时,便会在数据库中自动创建,当应用关闭时,则表会被删除。
也可以编程调用jbpmConfiguration.createSchema()和jbpmConfiguration.dropSchema()方法来实现表的创建和删除。
7.8 结合你自己的hibernate类
在你自己的项目中,你可能使用hibernate来持久化,可以使你自己的持久化类与Jbpm持久化类结合,这是可选的。这样做有两个主要的好处:
首先,会话、连接和事务管理变得容易。通过结合Jbpm和你自己的持久化在一个hibernate会话工厂,则只有一个hibernate会话、一个jdbc连接来处理你自己的和Jbpm的持久化,因此,Jbpm的更新与你自己的域模型更新在相同的事务中,这可以排除再使用一个事务管理器。
其次,这毫无争议的就把你自己的hibernate持久化对象融入了流程变量中。
可以通过创建一个核心的hibernate.cfg.xml来很容易的与Jbpm持久化类集成你自己的持久化类,你可以使用Jbpm中的src/config.files/hibernate.cfg.xml作为开始,然后在其中添加你自己的hibernate映射文件的引用。
7.9 定制Jbpm的hibernate映射文件
你可以按照如下方式来定制任何Jbpm的hibernate映射文件:
l 从源码(src/java.jbpm/…)或Jbpm库中拷贝Jbpm的hibernate映射文件
l 把拷贝的文件放置到classpath下任何你想要放置的地方
l 在hibernate.cfg.xml配置文件中更新指向自定义的映射文件
7.10 二级缓存
流程定义加载一次之后,Jbpm使用hibernate的二级缓存来把它放在内存中,流程定义类和集合在Jbpm的hibernate映射文件中使用cache元素来配置,如下:
<cache usage="nonstrict-read-write"/>
因为流程定义不会改变(应该不会改变),所以把它们放在二级缓存中是很好的做法,请参考“第 16.1.3 节 改变已发布的流程定义”。
二级缓存是JBOSS Jbpm实现的一个很重要的方面,如果没有这个缓存,JBOSS Jbpm在与其它实现BPM引擎的技术进行比较时,会是一个严重的缺陷。
缓存策略设置为nonstrict-read-write。在运行时,缓存策略可以设置为read-only,在这种情况下,你需要一个单独的hibernate映射文件来部署流程,这也是为什么我们选择nonstrict-read-write的原因。
第9章 流程建模
9.1 综述
流程定义(process definition)基于有向图表示了一个业务流程的规格化描述。图是由节点(node)和转换(transition)组成的,图中每个节点都有一个特定类型,节点的类型定义了运行时的行为。一个流程定义只能有一个开始状态。
令牌(token)是一个执行路线。令牌是运行时概念,它维护了一个指向图中节点的指针。
流程实例是(process instance)流程定义的执行。当一个流程实例创建后,一个令牌也为执行的主要路线创建了,这个令牌被称为流程实例的根令牌(root token),它被定位于流程定义的开始状态。
信号(signal)指示令牌继续图的执行。当令牌接收到一个没有命名的信号,它会经由缺省的离开转换离开它的当前节点;当一个转换名称在信号中被指定时,令牌会经由指定的转换离开节点。发送到流程实例的信号被委托给根令牌。
令牌进入节点后,节点被执行。节点自己有责任让图继续执行,图的继续执行是通过让令牌离开节点完成的。每个节点类型为了图的继续执行可以实现不同的行为,如果一个节点不能传播图的执行,则被表现为一个状态。
动作(Action)是在流程执行中的事件上被执行的java代码片断。在软件需求中,图是信息交流的一个重要手段,但是图只是将要生产的软件的一个视图(影像),它隐藏了很多技术细节。动作是在图的表示之外添加技术细节的一种机制,一旦图被做好,它可以由动作来修饰。主要的事件类型有:进入节点、离开节点、执行转换。
9.2 流程图
基本的流程定义是一个由节点和转换组成的图,这些信息在processdefinition.xml中表示。每个节点都有一个类型,如state、decision、fork、join等;每个节点有一组离开转换,可以给离开节点的每个转换一个名称来区分它们。例如:下图显示了jBay拍卖流程的流程图。
图 9.1拍卖流程图
下面是jBay拍卖流程图的xml表示:
<process-definition>
<start-state>
<transition to="auction" />
</start-state>
<state name="auction">
<transition name="auction ends" to="salefork" />
<transition name="cancel" to="end" />
</state>
<fork name="salefork">
<transition name="shipping" to="send item" />
<transition name="billing" to="receive money" />
</fork>
<state name="send item">
<transition to="receive item" />
</state>
<state name="receive item">
<transition to="salejoin" />
</state>
<state name="receive money">
<transition to="send money" />
</state>
<state name="send money">
<transition to="salejoin" />
</state>
<join name="salejoin">
<transition to="end" />
</join>
<end-state name="end" />
</process-definition>
9.3 节点
流程图是由节点和转换组成的,有关图的以及它的扩展模型的更多信息,请参考“第4章 面向图的编程”TODO。
每个节点有一个特定类型,节点类型决定了在运行时执行到达节点时将发生什么。Jbpm有一组你可以使用的预定义的节点类型,另外,你也可以编写定制代码来实现你自己指定的节点行为。
每个节点都有两个主要责任:首先,它可以执行普通java代码,典型情况下,java代码与节点功能是相关的,例如:创建一些任务实例、发送一个通知、更新一个数据库等;其次节点要负责传播流程执行。基本上来说,每个节点在传播流程执行时有以下几个可选方式:
1.不传播执行。这种情况下节点表现为一个等待状态。
2.经由节点的某个离开转换传播执行。这意味着到达本节点的令牌使用API调用executionContext.leaveNode()经由某个离开转换被传递,这时节点作为一个自动节点,它可以执行一些定制的程序逻辑然后自动继续流程执行,而没有等待。
3.创建一个新的执行路径。节点可以决定创建新的令牌,每个新的令牌表示一个新的执行路径,并且每个令牌可以通过节点的离开转换被启动。这种行为的一个很关好的例子就是fork节点。
4.结束执行路径。节点可以决定结束执行路径,这意味着令牌被结束,执行路径也就完结了。
5.更一般的情况,节点可以修改流程实例的整个运行时结构。运行时结构是包含一个令牌树的流程实例,每个令牌表示一个执行路径,节点可以创建和结束令牌,把每个令牌放在图中的节点,并且通过转换启动令牌。
Jbpm像其他任何工作流和BPM引擎一样,包含一组预实现的节点类型,它们有特定的文档配置和行为,但是关于Jbpm和面向图的编程基础(第4章 面向图的编程TODO)相对于其他来说其独特之处是对开发者开放模型,开发者可以很容易的编写自己的节点行为,并在流程中使用。
这也就是传统的工作流和BPM系统非常封闭之处,它们通常提供一组固定的节点类型(叫做流程语言),它们的流程语言是封闭的并且执行模型被隐藏在运行环境中。研究工作流模式可以发现,任何流程语言都不足够强大,我们决定做一个简单模型,并且允许开发者编写他们自己的节点类型,而JPDL流程语言则是完全开放的。
接下来我们论述JPDL中非常重要的节点类型。
9.3.2 节点类型task-node
任务节点代表一个或多个被人所执行的任务。因此当执行到达一个任务节点时,任务实例将会在工作流参与者的任务列表中被创建,然后,节点将表现为一个等待状态,当用户执行了他们的任务时,任务的完成会触发恢复执行,换句话说,这将导致一个新的信号在令牌上被调用。
9.3.3 节点类型state(状态)
状态就是一个等待状态,与任务节点不同的是没有任务实例在任务列表中被创建,如果流程需要等待一个外部系统,这是有用的。例如,在节点的入口处(通过node-enter时间的动作),可以发送一个消息到外部系统,然后流程进入等待状态,当外部系统发送一个响应消息时,这可以导致一个token.signal(),用来触发恢复流程执行。
9.3.4 节点类型decision(决策)
实际上有两种方式来建模决策,这两种方式之间的不同是基于“谁”来做决策,可以由流程(请阅读:在流程定义中指定)来做决策,或者由外部实体来提供决策结果。
当由流程来做决策时,就应该使用决策节点了。有两个基本的方法来指定决策标准:简单的方式是在转换上添加条件元素(condition),条件是返回一个布尔值的beanshell脚本表达式,在运行时,决策节点会循环它的离开转换(按照xml中指定的顺序)并计算每个条件,第一个条件结果返回为“true”的转换将被使用。另外一种可选方式是指定一个DecisionHandler的实现,然后决策在java类中被计算并且通过DecisionHandler实现的decide方法返回所选的离开转换。
当决策是由外部部分(意思是:不是流程定义的一部分)来决定时,你可以使用多个转换离开一个状态或等待状态节点,然后,离开转换可以被提供到外部,在等待状态结束后触发恢复执行,例如Token.signal(String transitionName)和TaskInstance.end(String transitionName)。
9.3.5 节点类型fork(分支)
分支把一个执行路径分裂成多个并发的执行路径。默认的分支行为是为每个离开分支的转换创建一个子令牌,并且与到达分支的令牌之间创建一个父子关系。
9.3.6 节点类型join(联合)
默认的联合假定到达联合的所有令牌都是同一父令牌的子令牌,这种情形当使用上面所提到的分支节点、并且当所有由分支创建的令牌到达同一联合时发生。联合会结束进入联合的每个令牌,然后联合会检查进入联合的令牌的父子关系,当所有的兄弟令牌都到达联合时,父令牌将会通过离开转换传播,当仍然还有活动的兄弟令牌时,联合表现为一个等待状态。
node节点类型适用于当你想要在节点中编写自己的代码的情形。node类型节点需要一个子元素动作(action),当执行到达节点时动作被执行,你写在actionhandler中的代码可以做任何事情,也包括节点必须自己负责传播执行(参考 9.3.1 节点责任)TODO。
如果你想要使用JavaAPI实现一些对业务分析者来说非常重要的功能逻辑,则可以使用node类型节点,通过使用node节点,节点在流程的图形表示中是可见的。作为对比,如果逻辑对于业务分析者来说是不重要的,则动作(下面将会介绍)允许你在流程的图形表示中添加不可见的代码。
9.4 转换(Transitions)
转换有一个源节点和一个目标节点,源节点用from属性表示,目标节点用to属性表示。
节点可以有一个任意的名称,注意:很多Jbpm特性依赖于唯一的转换名称,如果有多个转换有相同的名字,则拥有给定名称的第一个转换被使用。如果在一个节点中存在重复的转换名称,则Map getLeavingTransitionsMap()方法返回的元素将少于List getLeavingTransitions()方法。
默认的转换是列表中的第一个转换。
9.5 动作(Actions)
动作是在流程执行的事件上被执行的java代码片断。在软件需求交流中图是一种重要的工具,但是图仅仅是要生产的软件的一个视图(影射),它隐藏了很多技术细节,动作是一种在图形表示之外添加技术细节的机制。一旦图被做好,它就可以用动作来进行装饰,这意味着在不改变图的结构的情况下,java代码可以与图关联。主要的事件类型是进入节点、离开节点、执行转换。
注意动作被放置在事件与被放置在节点之间的差异。放置在事件中的动作当事件激活时被执行,事件上的动作不能影响流程的控制流,这很像观察者模式。另一方面,放置在node(参考 9.3.7 节点类型node )TODO上的动作则有传播流程执行的职责(参考9.3.1 节点责任)TODO。
让我们来看一个事件动作的例子。假设我们要在给点转换上做数据库更新,数据库更新是技术上必须的,但是对于业务分析者来说它是不重要的。
图 9.2 数据库更新动作
public class RemoveEmployeeUpdate implements ActionHandler {
public void execute(ExecutionContext ctx) throws Exception {
// get the fired employee from the process variables.
String firedEmployee = (String) ctx.getContextInstance().getVariable("fired employee");
// by taking the same database connection as used for the jbpm updates, we
// reuse the jbpm transaction for our database update.
Connection connection = ctx.getProcessInstance().getJbpmSession().getSession().getConnection();
Statement statement = connection.createStatement();
statement.execute("DELETE FROM EMPLOYEE WHERE ...");
statement.execute();
statement.close();
}
}
<process-definition name="yearly evaluation">
...
<state name="fire employee">
<transition to="collect badge">
<action class="com.nomercy.hr.RemoveEmployeeUpdate" />
</transition>
</state>
<state name="collect badge">
...
</process-definition>
9.5.1 动作配置
有关在processdefinition.xml中怎样对你定制的动作添加配置以及如何指定配置的更多信息,请参考“ 16.2.3 配置委托”TODO。
9.5.2 动作引用
动作可以指定名称,命名的动作可以在其他需要指定动作的地方来引用,命名的动作可以作为子元素被放入流程定义中。
这个特性在你想限制动作重复配置时非常有用(例如,当动作的配置复杂时)。另外一个用处就是执行或调度运行时动作。
9.5.3 事件
事件指定了流程执行中的时刻。Jbpm引擎在图执行期间会激活事件,这发生在Jbpm计算下一个状态时(请参阅:处理信号)。事件总是同流程定义中的元素相关,例如流程定义中的一个节点或转换。大多流程元素可以激活不同类型的事件,例如节点可以激活一个node-enter事件和一个node-leave事件。事件是同动作挂钩的,每个事件有一个动作清单,当Jbpm引擎激活事件时,清单中的动作被执行。
9.5.4 事件传播
超状态在流程定义的元素之间生成一个父-子关系,节点和转换被包含在作为父的超状态里,最顶级的元素以流程定义作为父,流程定义没有父。当一个事件被激活,事件将被向上传播至父层次,这允许在一个中心位置可以捕获到流程中的所有事件以及与事件相关联的动作。
9.5.5 脚本
脚本是动作执行的beanshell脚本,关于beanshell的更多信息,请参考beanshell站点。默认情况下,所有的流程变量可作为脚本变量和非脚本变量被写到流程变量中使用。以下脚本变量也可被使用:
l executionContext
l token
l node
l task
l taskInstance
<process-definition>
<event type="node-enter">
<script>
System.out.println("this script is entering node "+node);
</script>
</event>
...
</process-definition>
variable元素可以作为脚本的子元素,用来定制默认的加载和存储变量到脚本的行为。如果是那样的话,脚本表达式还必须被放入脚本的子元素expression内。
<process-definition>
<event type="process-end">
<script>
<expression>
a = b + c;
</expression>
<variable name='XXX' access='write' mapped-name='a' />
<variable name='YYY' access='read' mapped-name='b' />
<variable name='ZZZ' access='read' mapped-name='c' />
</script>
</event>
...
</process-definition>
在脚本开始之前,流程变量YYY和ZZZ分别作为脚本变量b和c将会被脚本所使用,脚本结束之后,脚本变量a的值被存储到流程变量XXX中。
如果变量的access属性包含“read”,则流程变量在脚本计算前作为脚本变量被加载;如果access属性包含“write”,则在脚本计算后脚本变量将被作为流程变量存储。属性mapped-name可以使流程变量在脚本中以另外一个名字使用,当你的流程变量包含空格或其他非法脚本字符时这很有用。
9.5.6 定制事件
注意,在流程执行期间激活你自己的定制事件是可能的。事件通过组合图元素(节点、转换、流程定义、超状态是图元素)和事件类型(java.lang.String)被唯一的定义。Jbpm定义了一组可由节点、转换和其他图元素激活的事件,但是作为一个用户,你可以自由的激活你自己的事件,在动作中、在你自己定制的节点执行中、或者甚至在流程实例执行之外,你都可以调用GraphElement.fireEvent(String eventType, ExecutionContext executionContext),事件类型的名称可以自由选择。
9.6 超状态(Superstates)
超状态是一组节点,超状态可以被递归嵌套。超状态可以被用来在流程定义中产生一些层次,例如,一个应用可能要把流程中的所有节点按阶段进行分组。动作可以与超状态事件关联,结果就是一个令牌在某个给定时间可以存在于多个嵌套的节点,这便于检查流程是否执行,比如,是否在启动阶段。在Jbpm模型中,你可以任意分组任何节点到一个超状态。
9.6.1 超状态转换
所有离开超状态的转换都可以被包含在超状态的节点里的令牌使用,转换也可以到达超状态,如果那样的话,令牌将被重定向到超状态中的第一个节点。超状态外部的节点可以拥有指向超状态内部的转换,同样,相反的,超状态内部的节点也可以拥有指向超状态外部或超状态自己的转换。超状态还可以拥有对它自己的引用。
9.6.2 超状态事件
有两个只有超状态才有的事件:superstate-enter和superstate-leave。无论通过哪个转换进入或离开节点,这些事件都会被激活。在超状态内部令牌执行转换时,这些事件不会被激活。
9.6.3 分级命名
节点在它的范围之内必须被唯一命名,节点的范围是它自己的节点集合,流程定义和超状态都是节点集合。在指向超状态内部节点时,你必须指定相对关系,用(/)隔开节点名称,用“..”指向上一层次。下面的例子展示了怎样指向一个超状态内部的节点:
<process-definition>
...
<state name="preparation">
<transition to="phase one/invite murphy"/>
</state>
<super-state name="phase one">
<state name="invite murphy"/>
</super-state>
...
</process-definition>
下面的例子展示了怎样向上指向超状态层次:
<process-definition>
...
<super-state name="phase one">
<state name="preparation">
<transition to="../phase two/invite murphy"/>
</state>
</super-state>
<super-state name="phase two">
<state name="invite murphy"/>
</super-state>
...
</process-definition>
9.7 异常处理
Jbpm的异常处理机制仅仅集中于java异常,图本身的执行不会导致问题,只有在执行委托类时才会导致异常。
在流程定义(process-definitions)、节点(nodes)和转换(transitions)上,可以指定一个异常处理(exception-handlers)清单,每个异常处理(exception-handler)有一个动作列表,当在委托类中发生异常时,会在流程元素的父层次搜索一个适当的异常处理(exception-handler),当它被搜索到,则异常处理(exception-handler)的动作将被执行。
注意,Jbpm的异常处理机制与java异常处理不完全相似。在java中,一个捕获的异常可以影响控制流,而在Jbpm中,控制流不会被Jbpm异常处理机制所改变。异常要么被捕获,要么不捕获,没有被捕获的异常被抛向客户端(例如客户端调用token.signal()),而被捕获的异常则是通过Jbpm的exception-handler,对于被捕获的异常,图执行仍会继续,就像没有异常发生一样。
注意,在处理异常的动作中,可以使用Token.setNode(Node node)把令牌放入图中的任何节点。
9.8 流程组合
在Jbpm中依靠process-state来支持流程的组合。process-state是与另外一个流程定义关联的状态,当图执行到达process-state,一个新的子流程实例被创建,并且它与到达process-state的执行路径相关联,超流程的执行路径将会等待,直到子流程实例结束。当子流程实例结束时,超流程的执行路径将离开process-state并在超流程中继续图的执行。
<process-definition name="hire">
<start-state>
<transition to="initial interview" />
</start-state>
<process-state name="initial interview">
<sub-process name="interview" />
<variable name="a" access="read,write" mapped-name="aa" />
<variable name="b" access="read" mapped-name="bb" />
<transition to="..." />
</process-state>
...
</process-definition>
这个“hire”流程包含一个产生“interview”流程的process-state,当执行到达“initial interview”,最新版本的“interview”流程的一个新的执行(相当于流程实例)被创建,然后来自“hire”流程的变量“a”被拷贝到来自“interview”流程的变量“aa”,同样,“hire”流程的变量“b”被拷贝到“interview”流程的变量“bb”。当“interview”流程结束时,只有“interview”流程的变量“aa”被拷贝回“hire”流程的变量“a”。
通常,当一个子流程被启动,在离开开始状态的信号被发出之前,所有拥有“read”存取属性的变量都被从超流程载入新创建的子流程;当子流程结束时,所有拥有“write”存取属性的变量都被从子流程拷贝到超流程。variabled元素的mapped-name属性允许你指定在子流程中将使用的变量名称。
9.9 定制节点行为
在Jbpm中很容易编写你自己的定制节点。为了创建定制节点,ActionHandler的一个实现必须被编写,这个实现可以执行任何业务逻辑,也必须包括传播图执行的职责。让我们看一个更新ERP系统的例子:我们将会从ERP系统中读取一个数量,然后加上一个存储在流程变量中的数量,再把结果存储回ERP系统;基于数量的大小,我们必须通过“small amounts”或“big amounts”转换离开节点。
图 9.3 更新erp流程示例片断
public class AmountUpdate implements ActionHandler {
public void execute(ExecutionContext ctx) throws Exception {
// business logic
Float erpAmount = ...get amount from erp-system...;
Float processAmount = (Float) ctx.getContextInstance().getVariable("amount");
float result = erpAmount.floatValue() + processAmount.floatValue();
...update erp-system with the result...;
// graph execution propagation
if (result > 5000) {
ctx.leaveNode(ctx, "big amounts");
} else {
ctx.leaveNode(ctx, "small amounts");
}
}
}
在定制的节点实现中可以创建和联合(join)令牌,关于如何做的示例,请签出Jbpm源码中fork和join节点的实现部分:-)
9.10 图执行
Jbpm的图执行模型基于的是流程定义的解释和命令链设计模式(chain of command pattern)。
解释流程定义意思是把流程定义存储到数据库中,在运行时流程执行期间流程定义信息被使用。需要注意的是:我们使用hibernate的二级缓存来避免在运行时加载流程定义信息,因为流程定义不会改变(参考流程版本部分),hibernate可以在内存中缓存流程定义。
命令链设计模式意味着图中每个节点负责传播流程执行,如果一个节点不传播执行,则它表现为一个等待状态。
这个意思是说流程实例开始执行之后一直执行,直到进入一个等待状态。
令牌代表一个执行路径,令牌拥有一个指向流程图中节点的指针,在等待状态,令牌可以被持久化到数据库。现在让我们看看计算令牌执行的算法:当一个信号发送到令牌时执行开始,然后执行通过转换和节点使用命令链设计模式被传递。下面是类图中的相关方法:
图 9.4 图执行相关方法
当令牌在节点内时,信号可以被发送到令牌,发送信号是开始执行的指示。信号必须指定令牌当前节点的离开转换,默认的是第一个离开转换。信号发送到令牌,令牌获取它的当前节点并且调用Node.leave(ExecutionContext, Transition)方法,可以把ExecutionContext看作令牌,因为ExecutionContext中的主要对象就是一个令牌。Node.leave(ExecutionContext, Transition)方法会激活转换事件并且调用转换的目标节点的Node.enter(ExecutionContext)方法,而Node.enter(ExecutionContext)方法会激活node-enter事件并且调用Node.execute(ExecutionContext)方法。每种类型的节点在execute方法中都有它们自己实现的行为,每个节点有责任通过再调用Node.leave(ExecutionContext, Transition)传播图的执行。概括如下:
l Token.signal(Transition)
l -->Node.leave(ExecutionContext, Transition)
l -->Transition.take(ExecutionContext)
l -->Node.enter(ExecutionContext)
l -->Node.execute(ExecutionContext)
注意,完成下一个状态的计算,包括调用动作是在客户端线程中完成的。一个误解是所有计算必须在客户端线程完成,象任何异步调用,你也可以使用异步消息(JMS)来完成。当消息被发送到与流程实例更新的同一事务中时,所有的同步问题必须小心对待。某些工作流系统在图的所有节点之间使用异步消息,除了在高吞吐量的环境里之外,这个算法可以给业务流程性能调整提供更多的控制和灵活性。
9.11 事务划分
象“9.10图执行”TODO和“第4章 面向图的编程”TODO中解释的那样,Jbpm在客户端线程中运行流程,并且自然使用同步。这个意思是说,token.signal()或者taskInstance.end()只有当流程进入一个新的等待状态时才会返回。
这里我们描述的JPDL特性来自于“第13章 异步继续”的建模观点。
因为流程执行可以很容易同事务服务绑定在一起,所以在很多情况下这是一个非常直观的方法:在一个事务中流程从一个状态到另一个状态。
在某些情形,流程中的某个计算会花费很长时间,这个行为是不受欢迎的,Jbpm包含一个允许以一种异步方式来继续流程的异步消息系统用来应对这种情况。当然,在java企业应用环境,Jbpm可以被配置为使用JMS消息代理,用来代替自己所构造的消息系统。
在某些节点,JPDL支持属性async=“true”,异步节点不会在客户端线程被执行,而是通过异步消息系统发送一个消息,并且线程被返回给客户端(意味着是token.signal()或taskInstance.end()返回)。
注意,现在Jbpm客户端代码可以提交事务。消息的发送应该与流程的更新在同一事务中完成。因此事务的最后结果是令牌被移动到下一节点(尚未被执行),并且一个org.jbpm.command.ExecuteNodeCommand消息在异步消息系统上被发送到Jbpm的命令执行器(Command Executor)。
Jbpm的命令执行器从队列中读取并执行命令,在org.jbpm.command.ExecuteNodeCommand下,流程通过执行节点被继续,每个命令在一个独立的事务中被执行。
因此,为了异步流程可以继续,必须需要运行一个Jbpm命令执行器,一个简单的方法是在你的Web应用中配置CommandExecutionServlet,作为选择,你应确保命令执行器线程可以运行于任何其他方式。
作为一个流程建模者,你不应该被这些异步消息所干预,主要关注点是事务划分:默认情况下Jbpm在客户端事务中运转,进行全部计算,直到进入一个等待状态。使用async=“true”在流程中划分事务。
让我们看一个例子:
...
<start-state>
<transition to="one" />
</start-state>
<node async="true" name="one">
<action class="com...MyAutomaticAction" />
<transition to="two" />
</node>
<node async="true" name="two">
<action class="com...MyAutomaticAction" />
<transition to="three" />
</node>
<node async="true" name="three">
<action class="com...MyAutomaticAction" />
<transition to="end" />
</node>
<end-state name="end" />
...
客户端代码与流程执行(开始和恢复)相符合,与普通的流程(同步的)一样:
...start a transaction...
JbpmContext jbpmContext = jbpmConfiguration.createContext();
try {
ProcessInstance processInstance = jbpmContext.newProcessInstance("my async process");
processInstance.signal();
jbpmContext.save(processInstance);
} finally {
jbpmContext.close();
}
在第一个处理之后,流程实例的根令牌将指向节点one,并且一个ExecuteNodeCommand消息被发送到命令执行器。
在并发的事务中,命令执行器从队列中读取消息,并且执行一个节点,动作可以决定传播执行或进入一个等待状态。如果动作决定传播执行,则当执行到达节点two时事务会被结束,以此类推…
第10章 上下文
上下文与流程变量有关,流程变量是维护与流程实例有关信息的“键-值”对,因为上下文必须存储在数据库中,所以使用会有一些约束。
10.1 访问变量
org.jbpm.context.exe.ContextInstance是提供流程变量服务的核心接口,你可以象如下这样从一个流程实例获取ContextInstance:
ProcessInstance processInstance = ...;
ContextInstance contextInstance = (ContextInstance) processInstance.getInstance(ContextInstance.class);
最基本的操作是:
void ContextInstance.setVariable(String variableName, Object value);
void ContextInstance.setVariable(String variableName, Object value, Token token);
Object ContextInstance.getVariable(String variableName);
Object ContextInstance.getVariable(String variableName, Token token);
变量名称是java.lang.String类型,默认情况下,Jbpm支持下列值类型:
l java.lang.String
l java.lang.Boolean
l java.lang.Character
l java.lang.Float
l java.lang.Double
l java.lang.Long
l java.lang.Byte
l java.lang.Short
l java.lang.Integer
l java.lang.Date
l byte[]
l java.io.Serializable
l 使用hibernate持久化的类
无类型的空值(null)也可以被持久化存储。
所有其他类型都可以毫无问题的被存储在流程变量里,但是当你试图保存流程实例时会导致一个异常。
要配置Jbpm存储变量中的hibernate持久化对象,请参考存储hibernate持久化对象部分。
10.2 变量生存期
变量不是必须在流程档案中声明。在运行时,你可以把任何java对象放进变量,如果这个变量不存在,它会被创建,就象使用一个普通的java.util.Map一样。
变量可以使用如下语句删除:
ContextInstance.deleteVariable(String variableName);
ContextInstance.deleteVariable(String variableName, Token token);
现在支持类型的自动转换,这意味着允许用一个不同类型的值来覆盖变量。当然,你应该尽量限制类型转换,因为这会造成比一个普通的列更新增加更多的数据库通信。
10.3 变量持久化
变量是流程实例的一部分,保存流程实例到数据库,使数据库与流程实例保持同步,作为保存(=更新)流程实例到数据库的结果,变量也将被从数据库创建、更新和删除。有关更多信息,请参考“第7章 持久化”。
10.4 变量范围
每个执行路径(参看:令牌)拥有它自己的一组流程变量,变量请求总是在令牌上发生。流程实例有一个令牌树(参看“第4章 面向图的编程”),当请求一个没有指定令牌的变量时,默认令牌是根令牌。
变量查询递归到给定令牌的父,这与编程语言中的变量范围是相似的。
当在令牌上设置一个不存在的变量时,变量在根令牌被创建,这就意味着每个变量默认都是整个流程范围的。如果想设置一个令牌局部变量,你必须明确使用如下语句创建:
ContextInstance.createVariable(String name, Object value, Token token);
10.4.1 变量重载
变量重载的意思是说,每个执行路径可以拥有它们自己的同名变量的拷贝,它们被看作是独立的,因此可以类型不同。当你通过同一转换发起多个并发的执行路径时,变量重载将会很有趣,区分这些执行路径的唯一之处就是它们各自独立的变量。
10.4.2 变量重写
变量重写的意思是说,在嵌套执行路径中的变量覆盖更高层执行路线中的变量。通常,嵌套执行路径涉及到并发:在分支和联合之间的执行路径是到达分支的执行路径的子(嵌套)。例如,如果你在流程实例范围有一个变量“contact”,你可以在嵌套的执行路径“shipping”和“billing”中重写这个变量。
10.4.3 任务实例变量范围
有关任务实例变量的更多信息,请参考“11.4 任务实例变量”。
10.5 临时变量
当流程实例被持久化到数据库时,正常的变量作为流程实例的一部分也被持久化。在某些情况下,你可能想在委托类中使用变量,但是你不想把它保存到数据库。例如,你想从Jbpm外部传递一个数据库连接到委托类,这可以使用临时变量来完成。
临时变量的生存期与流程实例(ProcessInstance)java对象一样。
由于这个自然性,临时变量与令牌无关,因此对于一个流程实例对象只有一个临时变量map。
临时变量使用上下文实例中它们自己的一组方法进行访问,并且不需要在processdefinition.xml中声明。
Object ContextInstance.getTransientVariable(String name);
void ContextInstance.setTransientVariable(String name, Object value);
10.6 定制变量持久化
变量保存到数据库中有两个步骤:
user-java-object <---> converter <---> variable instance
变量被保存在变量实例(VariableInstances)中,变量实例的成员使用hibernate被影射到数据库中的字段,在Jbpm的默认配置中,使用六种类型的变量实例:
l DateInstance(使用一个影射到数据库中Types.TIMESTAMP的java.lang.Date字段)
l DoubleInstance(使用一个影射到数据库中Types.DOUBLE的java.lang.Double字段)
l StringInstance(使用一个影射到数据库中Types.VARCHAR的java.lang.String字段)
l LongInstance(使用一个影射到数据库中Types.BIGINT的java.lang.Long字段)
l HibernateLongInstance(用于可被hibernate化类型的长整型id字段,一个java.lang.Object字段被作为对一个数据库中hibernate实体引用的影射)
l HibernateStringInstance(用于可被hibernate化类型的字符串id字段,一个java.lang.Object字段被作为对一个数据库中hibernate实体引用的影射)
转换器用来在java-user-objects和可以存储到变量实例的java对象之间进行转换,因此当一个流程变量使用如ContexInstance.setVariable(String variableName, Object value)被设置时,值将被使用转换器可选的转换,然后被转换的对象将被存储到变量实例。转换器实现了下面的接口:
public interface Converter extends Serializable {
boolean supports(Object value);
Object convert(Object o);
Object revert(Object o);
}
转换器是可选的,它必须被Jbpm类加载器(参考 16.2.1 Jbpm类加载器)可用。
user-java-objects被转换并且存储到变量实例的方式在文件org/jbpm/context/exe/jbpm.varmapping.xml中配置。要定制这个属性文件,需要把一个修改后的版本放在classpath根,就象在“6.2 配置文件”中解释的那样。属性文件的每行指定两个或三个用空格隔开的类名:user-java-object类名,可选的转换器类名和变量实例的类名。当你要引用你定制的转换器时,首先要确保它们在Jbpm的classpath(参考 16.2.1 Jbpm类加载器)。当你要引用你定制的变量实例时,它们仍然需要在Jbpm的classpath(参考16.2.1 Jbpm类加载器),并且hibernate的影射文件org/jbpm/context/exe/VariableInstance.hbm.xml必须更新来包含定制的变量实例的子类。
例如,看一下下面的在文件org/jbpm.context/exe/jbpm.varmapping.xml中的片断。
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.ClassNameMatcher">
<field name="className"><string value="java.lang.Boolean" /></field>
</bean>
</matcher>
<converter class="org.jbpm.context.exe.converter.BooleanToStringConverter" />
<variable-instance class="org.jbpm.context.exe.variableinstance.StringInstance" />
</jbpm-type>
这个片断指定了所有java.lang.Boolean类型的对象必须用转换器BooleanToStringConverter进行转换,并且转换结果对象(一个字符串)将被存储到一个StringInstance类型的变量实例对象里面。
如果没有指定转换器,则如下:
<jbpm-type>
<matcher>
<bean class="org.jbpm.context.exe.matcher.ClassNameMatcher">
<field name="className"><string value="java.lang.Long" /></field>
</bean>
</matcher>
<variable-instance class="org.jbpm.context.exe.variableinstance.LongInstance" />
</jbpm-type>
这表明变量中的Long类型对象存储在LongInstance类型变量实例中,不需要被转换。
第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活动图中借来的术语。
通过给不同的task指定相同的assignment,也可以达到多个任务分配给同一个参与者的目的。但是,这将产生不同的AssignmentHandler实例被调用。而使用泳道的话,则可以对于所有的任务分配,都使用相同的AssignmentHandler实例来进行。
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);
第12章 调度程序
本章描述在jBPM中怎样使用定时器。
在流程的事件之上,定时器可以被创建,当定时器到预定的期限时,动作被执行或者转换发生。
12.1 定时器
指定一个定时器最简单的方式是添加一个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>
定时器在节点上被指定,在节点被离开后不会被执行,转换和动作是可选的,当定时器被执行,下面事件将顺序发生:
l 一个timer类型的事件被激活。
l 如果指定了一个动作,则动作被执行。
l 如果指定了一个转换,一个信号将被发送,通过给定转换继续执行。
每个定时器必须有一个唯一的名称,如果在timer元素中没有指定名称,则节点的名称将被作为定时器的名称。
定时器动作可以是所支持的任何动作元素,如action或script。
定时器通过动作创建和取消,有两个动作元素create-timer和cancel-timer。事实上,上面所示的定时器元素只是create-timer动作依附于node-enter事件、cancel-timer动作依附于node-leave事件的一个简略表示。
12.2 调度程序部署
流程执行创建和取消定时器,定时器有专门的存储,一个独立的定时器运行器必须检查定时器存储并且在适当的时候执行定时器。
图 12.1调度程序组件
下面的类图展示了调度程序部署时相关的类,SchedulerService接口和TimerExecutor接口被指定用来使定时器执行机制是可插入的。
图12.2调度程序类
第13章 异步继续
13.1 概念
jBPM以面向图的编程(GOP)为基础,从基本上来讲,GOP指定了一个可以处理当前执行路径的简单状态机。在GOP中指定的执行算法中,所有状态的转换在客户端线程的一个单一操作中完成,如果你不熟悉在“第4章 面向图的编程”中定义的执行算法,请先阅读该部分。默认情况下,在客户端线程中完成状态转换是一个不错的方法,因为它自然地与服务端的事务保持一致,流程从一个等待状态到另一个等待状态的执行在一个事务中完成。
但是在某些情形开发者或许想在流程定义中调整事务划分。在jPDL中,可以使用属性async=“true”指定流程执行以异步的方式继续,可以在任何节点类型和动作类型上指定async=“true”。
13.2 一个实例
通常,节点总是在令牌进入之后被执行,因此,节点在客户端线程中被执行。我们将通过两个例子来研究异步执行,第一个例子有三个节点,它是一个流程的部分,节点“a”是一个等待状态,节点“b”是一个自动化步骤,节点“c”也是一个等待状态,下面的图中表现了这个流程不包含任何异步行为的情形。
第一个框图中展示了开始情形,令牌指向节点“a”,意味着执行路径正在等待一个外部的触发器,触发器必须通过发送一个信号到令牌引起。当信号到达时,令牌将被从节点“a”通过转换被传递到节点“b”,令牌到达节点“b”之后, 节点“b”被执行。回想一下,因为节点“b”是一个自动化步骤,它不会作为一个等待状态(例如发送一个email),所以第二个框图只是当节点“b”被执行时的一个快照,因为节点“b”是流程中的一个自动化步骤,所以节点“b”的执行包含了令牌通过转换到节点“c”的传播。节点“c”是一个等待状态,因此第三个框图展示了signal方法返回后的情形。
图 13.1 例1:没有异步继续的流程
虽然在jBPM中持久化不是必须的,但是通常情况下信号在一个事务中被调用。让我们看一下该事务的更新,首先,令牌被更新为指向节点“c”,这些更新作为GraphSession.saveProcessInstance的结果被hibernate在一个JDBC连接上发生;其次,如果自动化动作访问和更新某些相互影响的资源,那些资源的更新会与上述事务组合或作为事务的一部分。
现在我们看一下第二个例子,第二个例子是第一个例子的变种,在节点“b”引入了一个异步执行,节点“a”和“c”的行为表现与第一个例子中相同,也就是它们表现为等待状态。在jPDL中,一个节点通过设置属性async=“true”来标识为异步。
添加async=“true”到节点“b”的结果就是流程的执行将被分裂为两部分。一部分将执行流程直到节点“b”被执行;另一部分将会执行节点“b”,并且执行在等待状态“c”停止。
事务因此也将被分裂为两个独立的事务,每个事务对应于一部分。当在第一个事务中需要一个外部触发器(Token.signal方法调用)来离开节点“a”时,jBPM将会自动触发并完成第二个事务。
图 13.2 例2:有异步继续的流程
对于动作(action)原理相似,被属性async=“true”标识的动作在执行流程的线程之外被执行,如果持久化被配置(这是默认的),则动作在一个独立的事务中被执行。
在jBPM中,异步执行通过使用一个异步通知系统来实现。当流程执行到达需要异步执行的点时,jBPM将挂起执行,产生一个命令消息并发送该命令消息到命令执行器,命令执行器是一个单独的组件,在收到的消息之上它将在流程挂起的地方恢复流程执行。
jBPM可以被配置为使用JMS提供者或者它自己内置的异步通知系统,内置的通知系统在功能上是很有限的,但是可以在JMS不能使用的环境中支持异步特性。
13.3 命令执行器
命令执行器是恢复流程异步执行的组件,它通过异步通知系统等待命令消息的到达并执行它们,ExecuteNodeCommand和ExecuteActrionCommand两个命令被用来异步继续。
这些命令由流程执行产生,在流程执行过程中,对于每个需要异步执行的节点,一个ExecuteNodeCommand(POJO)将被在MessageInstance(消息实例)中创建,消息实例是ProcessInstance的一个非持久化扩展,它只是用来聚集所有将要发送的消息。
消息将被作为GraphSession.saveProcessInstance的一部分被发送,该方法的实现包含一个上下文构建器,构建器作为saveProcessInstance方法的一个方面(aspect)。实际使用的拦截器可以在jbpm.cfg.xml中配置,SendMessagesInterceptor拦截器被默认配置,用来从MessageInstance中读取消息并且通过可配置的异步通知系统发送消息。
SendMessagesInterceptor使用接口MessageServiceFactory和MessageService发送消息,这使得异步通知实现是可配置的(也是在jbpm.cfg.xml中)。
13.4 jBPM内置的异步消息
当使用jBPM内置的异步通知时,消息通过持久化到数据库被发送,消息的持久化可以与jBPM流程更新一样在同一个事务、jdbc连接中完成。
命令消息将被存储在JBPM_MESSAGE表中。
POJO命令执行器(org.jbpm.msg.command.CommandExecutor)将从数据库表中读取消息并执行它们,因此POJO命令执行器典型的事务处理如下:1)读取下一个命令消息2)执行命令消息3)删除命令消息。
如果命令消息执行失败,事务将会回滚,然后一个新的事务将被开始用来添加错误信息到数据库的消息,命令执行器过滤掉所有包含异常的消息。
图 13.3 POJO命令执行器事务
如果由于某些原因添加异常到命令消息的事务失败了,它也一样会被回滚,在这种情况下,没有异常的消息依然被保留在队列,因此它将在以后被再次尝试执行。
局限性:jBPM内置的异步通知系统不支持多个节点同步,因此你不能多次配置POJO命令执行器并且把它们配置为使用相同数据库。
13.5 JMS用于异步结构
异步执行特性打开了jBPM所适用场景的一个新局面,典型的被用在业务流程建模时,它可以从一个非常技术的观点来使用。
设想你有一个拥有很多异步处理的应用,绑定所有的消息生成和消息销毁软件在一起是很困难的,如果使用jBPM这将成为可能。通过创建所有异步结构的视图,把你的所有代码放入POJO,并且在流程文件中添加事务划分,就可以做到了。jBPM考虑到了你无需自己编写所有JMS或MDB代码而绑定发送者到接受者。
13.6 JMS用于异步通知
TODO(还未实现)
13.7 未来趋势
TODO:添加支持多个队列,因此可以为每个标识为异步的节点或动作指定一个队列,并且将循环为一组队列生成消息。因为所有这些对于JMS和内置的通知系统将是可配置的,所以对于怎样处理所有这些配置需要一些考虑。流程定义将无需依赖于这两个可能的实现。
第14章 业务日历
本章描述jBPM的业务日历,业务日历是关于业务时间的,并且被用于为任务和定时器计算预期的时间。
业务日历能够通过对一个期限和日期进行增加来计算日期。
14.1 期限
期限用一个绝对的或业务时间来指定,让我们看看语法:
<quantity>[business]<unit>
<quantity>是一个文本块,它是可以使用Double.parseDouble(quantity)所解析的;<unit>是{second,seconds,minute,minutes,hour,hours,day,days,week,weeks,month,months,year,years}之一;加上可选的business指示,意味着对于这个期限只有业务时间才应被考虑,如果没有business指示,期限将作为一个绝对的时间段被解释。
14.2 日历配置
文件org/jbpm/calendar/jbpm.business.calendar.properties指定了什么是业务时间,该配置文件可以被定制并且修改后的拷贝可被放在classpath根。
下面是业务时间规范的例子,它在jbpm.business.calendar.properties中被默认配置。
hour.format=HH:mm
#weekday ::= [<daypart> [& <daypart>]*]
#daypart ::= <start-hour>-<to-hour>
#start-hour and to-hour must be in the hour.format
#dayparts have to be ordered
weekday.monday= 9:00-12:00 & 12:30-17:00
weekday.thuesday= 9:00-12:00 & 12:30-17:00
weekday.wednesday= 9:00-12:00 & 12:30-17:00
weekday.thursday= 9:00-12:00 & 12:30-17:00
weekday.friday= 9:00-12:00 & 12:30-17:00
weekday.saturday=
weekday.sunday=
day.format=dd/MM/yyyy
# holiday syntax: <holiday>
# holiday period syntax: <start-day>-<end-day>
# below are the belgian official holidays
holiday.1= 01/01/2005 # nieuwjaar
holiday.2= 27/3/2005 # pasen
holiday.3= 28/3/2005 # paasmaandag
holiday.4= 1/5/2005 # feest van de arbeid
holiday.5= 5/5/2005 # hemelvaart
holiday.6= 15/5/2005 # pinksteren
holiday.7= 16/5/2005 # pinkstermaandag
holiday.8= 21/7/2005 # my birthday
holiday.9= 15/8/2005 # moederkesdag
holiday.10= 1/11/2005 # allerheiligen
holiday.11= 11/11/2005 # wapenstilstand
holiday.12= 25/12/2005 # kerstmis
business.day.expressed.in.hours= 8
business.week.expressed.in.hours= 40
business.month.expressed.in.business.days= 21
business.year.expressed.in.business.days= 220
第15章 记录日志
记录日志的目的是为了跟踪流程执行的历史,当流程执行的运行时数据变化时,所有增量都被保存到日志。
不要把本章所述的流程日志与软件日志相混淆,软件日志跟踪软件程序的执行(通常为了调试),而流程日志跟踪流程实例的执行。
对于流程日志信息有很多用途,非常明显的用途就是根据一个流程执行的参与者查阅流程历史。
另外一个用途就是业务活动监控(Business Activity Monitoring 简写BAM)。BAM将查询或分析流程执行日志,用来找出有关业务流程的有用的统计信息,例如:流程的每个步骤平均花费了多长时间?哪里是流程瓶颈?…在一个组织里,这些信息对于实现真正的业务流程管理是很关键的,真正的业务流程管理就是一个组织怎样管理它们的流程,怎样通过信息技术来支持流程,以及怎样在一个迭代过程中改善流程。
还有一个用途就是撤消功能,流程日志可被用于实现撤消。由于日志包含了运行时信息的增量数据,因此日志可以倒序播放,以把流程带回以前的状态。
15.1 创建日志
日志在运行流程执行时由jBPM模块产生,另外用户也可用插入流程日志。日志实体是一个继承自org.jbpm.logging.log.ProcessLog的java对象,流程日志实体被添加到LoggingInstance,LoggingInstance是ProcessInstance的一个可选的扩展。
jBPM生成多种日志:图执行日志,上下文日志和任务管理日志。有关这些日志包含的数据的更多信息,请查询javadocs,最好从org.jbpm.loggin.log.ProcessLog类开始,因为从这个类可导航到继承树。
LoggingInstance会聚集所有日志实体,当ProcessInstance被保存时,LoggingInstance中的所有日志将被清洗刷新(flush)到数据库,ProcessInstance的logs数据成员(译者注:logs应该是LoggingInstance的数据成员)并没有用hibernate映射,用来避免在每个事务中都从数据库中检索日志。每个ProcessLog在执行路径(Token)的上下文中被制造,因此ProcessLog引用令牌,而令牌也作为一个索引序号生成器为令牌里的ProcessLog索引提供服务,这对于日志检索是很重要的,这种方式下,在后续事务中产生的日志将有一个连续的序号(哇,这里会有许多序号:)。
对于发布来说日志是不重要的,在ProcessDefinition中去掉可选的LoggingDefinition就可以了,这将阻止LoggingInstance被捕获,从而也就没有日志将被维护。今后我们将在日志之上增加更加细粒度的配置控制,请参看jira“日志级别配置”。
下面是添加流程日志的API方法。
public class LoggingInstance extends ModuleInstance {
...
public void addLog(ProcessLog processLog) {...}
...
}
日志信息的UML图如下:
图 15.1 日志信息类图
CompositeLog是一类特别的日志实体,它作为许多子日志的父日志,因此可以在日志中创建一个层次结构,插入日志的API如下:
public class LoggingInstance extends ModuleInstance {
...
public void startCompositeLog(CompositeLog compositeLog) {...}
public void endCompositeLog() {...}
...
}
CompositeLog应该总是在一个try-finally块中被调用,以确保日志的层次结构是一致的。例如:
startCompositeLog(new MyCompositeLog());
try {
...
} finally {
endCompositeLog();
}
15.2 日志检索
如前面所说,日志不能通过导航LoggingInstance到它的日志从数据库中检索,作为替代,流程实例的日志总是可以从数据库中查询,LoggingSession有两个方法用作这个目的。
第一个方法检索一个流程实例的所有日志,这些日志在Map中根据令牌进行分组,map将为流程实例中的每个令牌关联一个ProcessLog列表,这个列表以与日志创建时的相同的顺序包含ProcessLog。
public class LoggingSession {
...
public Map findLogsByProcessInstance(long processInstanceId) {...}
...
}
第二个方法为指定的令牌检索日志,返回的列表以与日志创建时的相同的顺序包含ProcessLog。
public class LoggingSession {
public List findLogsByToken(long tokenId) {...}
...
}
15.3 数据仓库
有时你可能想要为jbpm流程日志使用数据仓库,数据仓库意味着你创建一个单独的包含流程日志的数据库,以用于不同的用途。
可能有很多理由使你为流程日志信息创建一个数据仓库,有时可能是为了从所使用的数据库中卸下繁重的查询,另外一种情况可能是为了做某些扩展的分析。可以对一个数据库模式(schema)进行改进优化以适应其目的来实现数据仓库。
在本章我们只是在jBPM中推荐了数据仓库技术,用途是多变的,防止在jBPM中包含一个通用的解决方案来覆盖所有的需求。
第16章 jBPM流程定义语言(JPDL)
JPDL指定了xml模式和打包所有流程定义相关文件到一个流程档案的机制。
16.1 流程档案
一个流程档案就是一个zip文件,流程档案中的核心文件是processdefinition.xml,该文件的主要信息是流程图,processdefinition.xml文件还包括有关动作和任务的信息。流程档案也可以包含其他流程相关文件,如classes(类的字节码文件)、任务的ui-forms(任务的用户界面窗体)、…
16.1.1 部署流程档案
可以用3种方式部署流程档案:用流程设计器工具,用ant任务或编程。
使用设计器工具部署流程档案仍然在构建阶段。
使用ant任务部署流程档案可以按照如下方式:
<target name="deploy.par">
<taskdef name="deploypar" classname="org.jbpm.ant.DeployParTask">
<classpath --make sure the jbpm-[version].jar is in this classpath--/>
</taskdef>
<deploypar par="build/myprocess.par" />
</target>
要一次部署多个流程档案,可以使用嵌套的fileset元素,file属性是可选的。Ant任务的其他属性是:
l cfg:cfg是可选的,默认值是“hibernate.cfg.xml”,hibernate配置文件包含jdbc连接数据库的属性以及映射文件。
l properties:properties是可选的,并且覆盖所有hibernate.cfg.xml文件中的hibernate相同属性。
l createschema:如果设置为true,则jbpm数据库模式(数据表)在流程部署前被创建。
流程档案还可以通过使用类org.jbpm.jpdl.par.ProcessArchiveDeployer编程被部署。
16.1.2 流程版本
流程定义不应该改变,因为预测流程变化带来的所有可能的影响是非常困难的(或者说是不可能的)。
围绕这个问题,jBPM有一个明智的流程版本机制。版本机制允许在数据库中多个同名流程定义共存,流程实例以当时的最新版本来启动,并且在它的整个生命周期中将保持以相同的流程定义执行。当一个新的版本被部署,新的流程实例以新版本启动,而老的流程实例则以老的流程定义继续执行。
流程定义是指定的流程图以及其他一组相关的java classes(字节码文件)的结合体,有两种方式可以使java classes在jBPM运行环境是可用的:确保这些java classes对于jBPM类装载器是可见的,这通常意味着你可以把委托classes放入一个.jar文件,然后再放入jbpm-[version].jar文件;另外,java classes也可以被包含在流程档案中,当在流程档案中包含自己的委托classes时(它们不被jbpm类装载器可见),jBPM也将在这些classes上应用版本机制。有关流程类装载的更多信息可以在“16.2委托”中找到。
当一个流程档案被部署时,将在jBPM数据库中创建一个流程定义,流程定义基于流程定义的名称被版本化。当一个被命名的流程档案被部署,部署器将分配一个版本号。为了分配版本号,部署器将查询同名流程定义的最高版本号,并且在其上加1,未命名的流程定义其版本号总是-1。
16.1.3 改变已部署的流程定义
在部署到jBPM数据库之后改变流程定义有很多潜在的缺陷,因此非常不鼓励这样做。
事实上,对于流程定义会有很多可能的改变,这些流程定义中的某些也许是无害的,但是改变意味着会更合理。
因此请考虑通过这种途径移植流程实例到新的定义。
如果你打算这么做,下面是需要考虑的点:
使用hibernate的更新:你可以加载一个流程定义,改变它并且使用hibernate session保存它。Hibernate session可以用JbpmContext.getSession()方法获取。
二级缓存:在你更新一个存在的流程定义之后,该流程定义需要从二级缓存中移除,请参看“第7.10节二级缓存”。
16.1.4 移植流程实例
改变流程定义可能会要求转换执行(正在执行的流程实例)到一个新的流程定义,由于业务流程的持续性,考虑到这不是没有价值的。当前,这还是一个实验区域,还没有在支持范围之内。
就象你所了解的那样,流程定义数据、流程实例数据(运行时数据)、以及日志数据有明显的区别。使用这种方式,在jBPM数据库中创建一个独立的新的流程定义(例如,可以部署一个相同流程的新的版本),然后运行时信息被转换到新的流程定义。这可能包括导致一个旧流程指向的节点在新流程中已被移除的转化,因此,仅仅新的数据才在数据库中被创建,但是这样一个流程执行被遍布在两个流程实例对象上,这可能会成为工具和统计计算中棘手的部分。当资源允许时,我们将在以后做支持,例如,流程实例上会添加一个指向其前一个实例的指针。
16.1.5 流程变换
一个转换类用来帮助你把jBPM2.0流程档案转换到jBPM3.0兼容的流程档案。创建一个用来放置转换后流程档案的输出目录,从jBPM3.0分发的构建目录键入下面命令行:
java –jar converter.jar 输入目录 输出目录
用你的jBPM2.0流程档案位置替换“输入目录”,用创建的放置新的转换后流程档案的目录替换“输出目录”。
16.2 委托
委托是用来在流程执行中包含用户自定义代码的机制。
16.2.1 jBPM类装载器
jBPM类装载器是装载jBPM类的装载器,这意味着jbpm-3.x.jar库在类装载器的classpath中。要使类对于jBPM类装载器可见,就把它们放入一个jar文件,并把这个jar文件放入jbpm-3.x.jar,或者放入web应用的WEB-INF/lib文件夹下。
16.2.2 流程类装载器
委托类由它们各自流程定义的流程类装载器来装载,流程类装载器是以jBPM类装载器为父的类装载器。流程类装载器添加某个流程定义的所有类,你可以通过把类放入流程档案的/classes文件夹而把类添加到流程定义。注意,这仅仅当你想要把所添加的类版本化时才有用。
如果版本化不是必须的,则使类对于jBPM类装载器可见效率会更高。
16.2.3 委托配置
委托类包含在流程执行中将被调用的用户代码,最一般的例子就是动作(action),在这种情况下,ActionHandler接口的一个实现在流程事件上被调用。委托在processdefinition.xml中被指定,当指定一个委托时可以提供3部分的数据:
l 类名(必需的):委托类的完整类名。
l 配置类型(可选的):指定实例化和配置委托类对象的方式。默认情况下,默认的构造器被使用并且配置信息被忽略。
l 配置(可选的):按照配置类型所要求格式对委托对象的配置信息。
下面是所有配置类型的描述:
16.2.3 .1配置类型field
这是默认的配置类型,配置类型field将首先实例化一个委托类的对象,然后按照配置中的指定将值设置到对象的成员(field)中。配置是xml格式的,其元素与类的成员名称一致,元素的内容文本被放入相应的成员。如果是必需且可能的,则元素的内容文本被转换为成员类型。
所支持的类型转换:
l String当然不需要转换,但是它会被修整(trim)。
l 原始类型,例如int、long、float、double、…
l 原始类型的基本包装类。
l 列表(list)、集合(set)、以及聚集(collection)。这种情况下,xml内容的每个元素被看作聚集的一个元素,并且在转换中被递归的解析。如果元素类型与java.lang.String不同,则需要通过为类型属性指定一个完整类型名来标出。例如,下面片断将把一个字符串的ArrayList注入成员“numbers”:
<numbers>
<element>one</element>
<element>two</element>
<element>three</element>
</numbers>
元素中的文本可以被转换为任何拥有一个String构造器的对象,要使用String类型之外的其他类型,则在成员元素(本例中的“numbers”)中指定element-type。
下面是另外一个map的例子:
<numbers>
<entry><key>one</key><value>1</value></entry>
<entry><key>two</key><value>2</value></entry>
<entry><key>three</key><value>3</value></entry>
</numbers>
l maps。这种情况,每个成员元素需要有一个子元素key和一个元素value。key和value都被递归的使用转换规则解析,与聚集(collection)完全一样,如果没有类型属性被指定,则假定转换到java.lang.String。
l org.dom4j.Element
l 其他类型,字符串构造器被使用。
例如下面的类…
public class MyAction implements ActionHandler {
// access specifiers can be private, default, protected or public
private String city;
Integer rounds;
...
}
…下面是有效的配置:
...
<action class="org.test.MyAction">
<city> Atlanta </city>
<rounds>5</rounds>
</action>
...
16.2.3 .2配置类型bean
与field配置类型相似,除了属性被通过setter方法设置之外,而不是直接设置到成员上,使用相同的转换规则。
16.2.3 .3配置类型constructor
这种实例化会取委托的xml元素的所有内容,并且把它们作为文本传入委托类的构造器。
16.2.3 .4配置类型configuration-property
首先,使用默认的构造器,然后这种实例化将取出委托xml元素的所有内容,并且把它们作为文本传入方法void configure(String)。(就像jBPM2中一样)
16.3 表达式
对于某些委托,支持一种象JSP/JSF EL那样的表达式语言。在动作(actions)、分配(assignments)和决策(decision)条件中,你可以写一个如expression=“#{myVar.handler[assignments].assign}”的表达式。
这种表达式语言的基础知识可以在J2EE指南中找到。
jPDL表达式语言与JSF表达式语言相似,jPDL EL基于JSP EL,除了使用#{…}符号以及它包括对方法绑定的支持之外。
依赖于上下文,流程变量或者任务实例变量连同下面的内置对象可以被作为变量使用:
l taskInstance(org.jbpm.taskmgmt.exe.TaskInstance)
l processInstance(org.jbpm.graph.exe.ProcessInstance)
l token(org.jbpm.graph.exe.Token)
l taskMgmtInstance(org.jbpm.taskmgmt.exe.TaskMgmtInstance)
l contextInstance(org.jbpm.context.exe.ContextInstance)
这个特性在Jboss SEAM环境中变得真正强大,因为jBPM与Jboss SEAM的集成,所有你自己的bean、EJB和其他某种素材在你的流程定义中变得直接可用。
16.4 jBPM XML 模式
jPDL模式是流程档案里的processdefinition.xml文件中所使用的模式。
16.4.1 确认
在解析一个jPDL XML文件时,当遇到两种情形时jBPM将靠jPDL模式验证你的文档:第一种,模式在XML文件中被引用,如下
<process-definition xmlns="urn:jbpm.org:jpdl-3.1">
...
</process-definition>
第二种,xerces解析器被放置在了classpath。
jPDL模式可以在${jbpm.home}/src/java.jbpm/org/jbpm/jpdl/xml/jpdl-3.1.xsd或http://jbpm.org/jpdl-3.1.xsd中找到。
16.4.2 p rocess-definition
表格 16.1
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 流程的名称。 |
元素 | [0..*] | 流程中使用的泳道。泳道表示流程角色,它们被用于任务分配。 | |
元素 | [0..1] | 流程起始状态。注意,没有起始状态的流程是合法的,但是不能被执行。 | |
{end-state|state|node|task-node|process-state|super-state|fork|join|decision} | 元素 | [0..*] | 流程定义的节点。注意,没有节点的流程是合法的,但是不能被执行。 |
元素 | [0..*] | 作为一个容器服务于动作的流程事件。 | |
元素 | [0..*] | 全局定义的的动作,可以在事件和转换中引用。注意,为了被引用,这些动作必须指定名称。 | |
元素 | [0..*] | 全局定义的任务,可以在动作中使用。 | |
元素 | [0..*] | 一个异常处理器列表,用于这个流程定义中的委托类所抛出的所有异常。 |
16.4.3 node
表格 16.2
名称 | 类型 | 多样性 | 描述 |
事件 | 1 | 用于表示这个节点行为的定制动作。 | |
|
| 请参考普通节点元素。 |
16.4.4 普通节点元素
表格 16.3
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 必需的 | 节点的名称。 |
async | 属性 | {true|false},默认是false | 如果设置为true,这个节点将会异步执行。请参考“第13章异步继续”。 |
元素 | [0..*] | 离开转换。每个离开节点的转换必须有一个不同的名称,最多只允许所有离开转换中的一个没有名称。第一个转换被指定为默认转换,当离开节点而没有指定转换时,默认转换发生。 | |
元素 | [0..*] | 支持的事件类型:{node-enter|node-leave}。 | |
元素 | [0..*] | 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 | |
元素 | [0..*] | 指定一个定时器,用来监视节点中的一个执行所持续的时间。 |
16.4.5 start-state
表格 16.4
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 节点的名称。 |
元素 | [0..1] | 起始一个流程实例的任务,或者用来捕获流程发起者,请参考“第11.7节开始任务中的泳道”。 | |
元素 | [0..*] | 支持的事件类型:{node-leave}。 | |
元素 | [0..*] | 离开转换,每个离开节点的转换必须有一个不同的名称。 | |
元素 | [0..*] | 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 |
16.4.6 end-state
表格 16.5
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 必需的 | 结束状态的名称。 |
元素 | [0..*] | 支持的事件类型:{node-enter}。 | |
元素 | [0..*] | 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 |
16.4.7 state
表格 16.6
名称 | 类型 | 多样性 | 描述 |
|
| 请参考普通节点元素。 |
16.4.8 task-node
表格 16.7
名称 | 类型 | 多样性 | 描述 |
signal | 属性 | 可选的 | {unsynchronized|never|first|first-wait|last|last-wait},默认是last。signal指定了任务的完成对流程执行继续的影响。 |
create-tasks | 属性 | 可选的 | {yes|no|true|false},默认是true。当需要在运行时通过计算来决定哪个任务将被创建时,可以设置为false,如果这样的话,在node-enter事件上加一个动作,在动作中创建任务,并且把create-tasks设置为false。 |
end-tasks | 属性 | 可选的 | {yes|no|true|false},默认是false。如果设置end-tasks为true,在离开节点时,所有打开的任务将被结束。 |
元素 | [0..*] | 当执行到达本节点时所应被创建的任务。 | |
|
| 请参考普通节点元素。 |
16.4.9 p rocess-state
表格 16.8
名称 | 类型 | 多样性 | 描述 |
元素 | 1 | 与被节点相关联的子流程。 | |
元素 | [0..*] | 指定在子流程发起时数据如何从超流程拷贝到子流程,以及在子流程结束时数据如何从子流程拷贝到超流程。 | |
|
| 请参考普通节点元素。 |
16.4.10 super-state
表格 16.9
名称 | 类型 | 多样性 | 描述 |
{end-state|state|node|task-node|process-state|super-state|fork|join|decision} | 元素 | [0..*] | 超状态节点,超状态可以嵌套。 |
|
| 请参考普通节点元素。 |
16.4.11 fork
表格 16.10
名称 | 类型 | 多样性 | 描述 |
|
| 请参考普通节点元素。 |
16.4.12 join
表格 16.11
名称 | 类型 | 多样性 | 描述 |
|
| 请参考普通节点元素。 |
16.4.13 decision
表格 16.12
名称 | 类型 | 多样性 | 描述 |
元素 | 要么指定“handler”元素,或者在转换上指定条件。 | 一个org.jbpm.jpdl.Def.DecisionHandler的实现名称。 | |
元素 | [0..*] | 离开转换。决策的离开转换可以被扩展为拥有一个条件,决策会查找条件计算为true的第一个转换,没有条件的转换被认为计算为true(为了建模“otherwise”分支)。请参考condition元素。 | |
|
| 请参考普通节点元素。 |
16.4.14 event
表格 16.13
名称 | 类型 | 多样性 | 描述 |
type | 属性 | 必需的 | 表示相对于事件要放置的元素事件类型。 |
元素 | [0..*] | 在这个事件上将要执行的动作列表。 |
16.4.15 transition
表格 16.14
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 转换的名称。注意,每个节点的离开转换必须有一个不同的名称。 |
to | 属性 | 必需的 | 目标节点的分级名称,有关分级名称的更多信息请参考“第9.6.3节分级命名”。 |
元素 | [0..*] | 发生转换时将要执行的动作。注意,转换的动作无需放入事件(因为只有一个事件)。 | |
元素 | [0..*] | 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 |
16.4.16 a ction
表格 16.15
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 必需的 | 动作的名称。当动作被指定名称后,它们可以在流程定义中被查出,这对于运行时动作以及仅一次声明动作是有用的。 |
class | 属性 | 或者用ref-name,或者用expression。 | 实现org.jbpm.graph.def.ActionHandler接口的类的全名。 |
ref-name | 属性 | 或者用class。 | 所引用动作的名称。如果指定一个引用动作,则本动作不需要再做处理。 |
expression | 属性 | 或者指定一个class,或者ref-name。 | 一个解决一个方法的jPDL表达式。请参考“第16.3节表达式”。 |
accept-propagated-events | 属性 | 可选的 | {yes|no|true|false},默认是yes|true。如果设置为false,则动作仅在本动作元素的触发事件上被执行。更多信息,请参考“第9.5.4 事件传播”。 |
config-type | 属性 | 可选的 | {field|bean|constructor|configuration-property}。指定动作对象将被怎样创建以及本元素的内容怎样象配置信息那样被动作对象所使用。 |
async | 属性 | {true|false} | 默认false,这意味着动作将在当前执行的线程中被执行。如果设置为true,一个消息将被发送到命令执行器,并且执行器组件将在一个独立的事务中同步执行动作。 |
| {内容} | 可选的 | action的内容可以被作为你定制动作实现的配置信息,这是考虑到可重用的委托类的创建。有关委托配置的更多信息,请参考“第16.2.3节委托配置”。 |
16.4.17 script
表格 16.16
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 脚本动作的名称。当动作被指定名称后,它们可以在流程定义中被查出,这对于运行时动作以及仅一次声明动作是有用的。 |
accept-propagated-events | 属性 | 可选的[0..*] | {yes|no|true|false},默认是yes|true。如果设置为false,则动作仅在本动作元素的触发事件上被执行。更多信息,请参考“第9.5.4 事件传播”。 |
expression | 元素 | [0..1] | beanshell脚本。如果你没有指定variable元素,可以写表达式作为脚本元素的内容(忽略expression元素标签)。 |
元素 | [0..*] | 脚本所需变量。如果没有指定变量,则当前令牌的所有变量将被装载到脚本,当你想要限制装载到脚本中的变量数量时使用variable。 |
16.4.18 expression
表格 16.17
名称 | 类型 | 多样性 | 描述 |
| {内容} |
| 一个beanshell脚本。 |
16.4.19 variable
表格 16.18
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 必需的 | 流程变量的名称。 |
access | 属性 | 可选的 | 默认是read,write,用逗号分割的一个访问列表。迄今为止,使用的访问仅为read,write和required。 |
mapped-name | 属性 | 可选的 | 默认是变量的名称。用来指定变量名称被映射的名称,mapped-name的含义依赖于这个元素所被使用的上下文。对于一个脚本,将是一个脚本变量名称;对于一个任务控制器,将是任务表单参数的标签;对于一个process-state,将是在子流程中使用的变量名称。 |
16.4.20 handler
表格 16.19
名称 | 类型 | 多样性 | 描述 |
expression | 属性 | 或者用class | 一个jPDL表达式,返回结果被用toString()方法转换为字符串,结果字符串应该与某个离开转换匹配。请参考“第16.3 表达式”。 |
class | 属性 | 或者用ref-name | 实现了org.jbpm.graph.node.DecisionHandler接口的类的全名。 |
config-type | 属性 | 可选的 | {field|bean|constructor|configuration-property}。指定动作对象将被怎样创建以及本元素的内容怎样象配置信息那样被动作对象所使用。 |
| {内容} | 可选的 | action的内容可以被作为你定制动作实现的配置信息,这是考虑到可重用的委托类的创建。有关委托配置的更多信息,请参考“第16.2.3节委托配置”。 |
16.4.21 timer
表格 16.20
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 定时器的名称。如果没有指定名称,则采用外部的节点名称。注意,每个定时器应该有一个唯一的名称。 |
duedate | 属性 | 必需的 | 所指定的定时器创建到定时器执行之间的期限(可以用业务时间来表示)。请参考“第14.1节期限”中的语法。 |
repeat | 属性 | 可选的 | {duration|’yes’|’true’}当一个定时器在预期时间执行后,“repeat”可选项指定了在离开节点之前重复的执行定时器之间的期限。如果指定为true或yese,则与duedate相同的期限被使用。请参考“第14.1节期限”的语法。 |
transition | 属性 | 可选的 | 当定时器执行、定时器事件触发后以及执行动作时时所使用的转换名称。 |
cancel-event | 属性 | 可选的 | 这个属性只用在任务的定时器中,它指定了定时器将被取消的事件。默认是task-end事件,但是也可以被设置为如task-assign或task-start。cancel-event的类型也可以通过指定一个用逗号分割的列表被组合。 |
元素 | [0..*] | 当定时器被触发时所应被执行的动作。 |
16.4.22 create-timer
表格 16.21
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 定时器的名称。这个名称可被用于用一个cancel-timer动作取消定时器。 |
duedate | 属性 | 必需的 | 所指定的定时器创建到定时器执行之间的期限(可以用业务时间来表示)。请参考“第14.1节期限”中的语法。 |
repeat | 属性 | 可选的 | {duration|’yes’|’true’}当一个定时器在预期时间执行后,“repeat”可选项指定了在离开节点之前重复的执行定时器之间的期限。如果指定为true或yese,则与duedate相同的期限被使用。请参考“第14.1节期限”的语法。 |
transition | 属性 | 可选的 | 当定时器执行、定时器事件触发后以及执行动作时时(如果要)所获取的转换名称。 |
16.4.23 cancel-timer
表格 16.22
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 要被取消的定时器的名称。 |
16.4.24 task
表格 16.23
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 可选的 | 任务的名称。命名的任可以被引用并且可以通过TaskMgmtDefinition被查出。 |
blocking | 属性 | 可选的 | {yes|no|true|false},默认是false。如果blocking设置为true,当任务没有结束时节点不能被离开;如果设置为false(默认),令牌上的一个新号被用来继续执行并离开节点。默认设置为false,因为通常是由用户接口来强制阻塞。 |
signalling | 属性 | 可选的 | {yes|no|true|false},默认是true。如果设置signalling为false,则本任务将没有触发令牌继续的能力。 |
duedate | 属性 | 可选的 | 一个绝对的期限表达式或象“第14章业务日历”中解释的业务时间。 |
swimlane | 属性 | 可选的 | 引用一个swimlane,如果在任务上指定了一个swimlane,则assignment将被忽略。 |
priority | 属性 | 可选的 | {highest,high,normal,low,lowest}之一。作为选择,可以为priority指定任何整数,供参考:(highest=1,lowest=5)。 |
元素 | 可选的 | 描写一个委托,该委托将在任务被创建时把任务分配给一个参与者。 | |
元素 | [0..*] | 支持的事件类型:{task-create|task-start|task-assign|task-end}。为了任务分配,我们特别的为TaskInstance添加了一个非持久化的属性previousActorId。 | |
元素 | [0..*] | 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 | |
元素 | [0..*] | 指定一个监视本任务执行期限的一个定时器。对于任务定时器特殊的是可以指定cancel-event,cancel-event默认是task-end,但是它可以被自定义如task-assign或task-start。 | |
元素 | [0..1] | 指定流程变量怎样被转换为任务表单参数。任务表单参数有用户界面使用,用力向用户表现一个任务表单。 |
16.4.25 swimlane
表格 16.24
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 必需的 | 泳道的名称。泳道可以被引用并且可以通过TaskMgmtDefinition被查出。 |
元素 | [1..1] | 指定泳道的分配。这个分配在本泳道中的第一个任务实例被创建时完成。 |
16.4.26 a ssignment
表格 16.25
名称 | 类型 | 多样性 | 描述 |
expression | 属性 | 可选的 | 由于历史原因,这个属性的表达式不是jPDL表达式,而是对jBPM身份组件的一个分配表达式。有关怎样写jBPM身份组件表达式的更多信息,请参考“第11.11.2节分配表达式”。注意,这个依赖于jbpm身份组件。 |
actor-id | 属性 | 可选的 | 一个actorId,可以与pooled-actors协同使用。actor-id被作为一个表达式,因此你可以引用一个固定的actorId,如actor-id=”bobthebuiler”;或者你可以引用一个可以返回一个字符串的属性或方法,如actor-id=”myVar.actorId”,这将调用任务实例变量“myVar”上的getActorId方法。 |
pooled-actors | 属性 | 可选的 | 一个逗号分割的actorId列表,可以与actor-id协同使用。一个固定的参与者池可以指定如下:pooled-actors=”chicagobulls,pointersisters”。 pooled-actors被作为一个表达式,因此你可以引用一个返回String[]、Collection、或一个逗号分割的池中的参与者列表的属性或方法。 |
class | 属性 | 可选的 | 一个实现org.jbpm.taskmgmt.def.AssignmentHandler接口的类的全名称。 |
config-type | 属性 | 可选的 | {field|bean|constructor|configuration-property}。指定分配处理器对象(assignment-handler-object)对象将被怎样创建以及本元素的内容怎样象配置信息那样被分配处理器对象所使用。 |
| {内容} | 可选的 | assignment元素的内容可以被作为分配处理器(AssignmentHandler)实现的配置信息,这是考虑到可重用的委托类的创建。有关委托配置的更多信息,请参考“第16.2.3节委托配置”。 |
16.4.27 controller
表格 16.26
名称 | 类型 | 多样性 | 描述 |
class | 属性 | 可选的 | 一个实现org.jbpm.taskmgmt.def.TaskControllerHandler接口的类的全名称。 |
config-type | 属性 | 可选的 | {field|bean|constructor|configuration-property}。指定分配处理器对象(assignment-handler-object)对象将被怎样创建以及本元素的内容怎样象配置信息那样被分配处理器对象所使用。 |
| {内容} |
| controller元素的内容要么是指定的任务控制处理器的配置信息(如果指定了class属性),要么必须是一个variable元素列表(如果没有指定任务控制器)。 |
元素 | [0..*] | 如果没有通过class属性指定任务控制处理器,则controller元素的内容必须是变量列表。 |
16.4.28 sub-process
表格 16.27
名称 | 类型 | 多样性 | 描述 |
name | 属性 | 必需的 | 子流程的名称。要知道怎样测试字流程,请参考“第18.3节测试子流程”。 |
version | 属性 | 可选的 | 子流程的版本。如果没有指定版本,则给定流程的最新版本将被使用。 |
16.4.29 condition
表格 16.28
名称 | 类型 | 多样性 | 描述 |
| {内容}或属性 表达式 | 必需的 | condition元素的内容是一个计算结果为布尔值的jPDL表达式。决策采用第一个表达式处理结果为true的转换(按在processdefinition.xml中的顺序),如果没有条件处理结果为true,则采用默认离开转换(也及时第一个)。 |
16.4.30 exception-handler
表格 16.29
名称 | 类型 | 多样性 | 描述 |
exception-class | 属性 | 可选的 | 指定与本异常处理器所匹配的java throwable类,如果这个没有指定这个属性,则它匹配所有异常(java.lang.Throwable)。 |
元素 | [1..*] | 当异常被异常处理器捕获时将要执行的动作列表。 |
第18章 工作流TDD(测试驱动开发)
18.1 工作流TDD简介
因为面向流程的软件开发与其它软件没有什么不同,所以我们认为流程定义应该可以被容易的测试。本章将向你展示怎样用平常的JUnit对所创建的流程定义进行单元测试,而不需要做任何扩展。
开发周期应该尽可能的短,对软件源码的修改应该立即被验证,最好没有任何中间的构造步骤。下面的示例将向你展示怎样开发和测试jBPM流程而没有中间步骤。
通常流程定义的单元测试是执行一个场景,每个场景在JUnit的测试方法中被执行,并且将外部的触发(请阅读:信号,即signal)传回到流程执行中,然后在每个信号之后验证流程是否处于所预期的状态。
让我们看这样一个测试例子,我们使用拍卖流程的一个简化版本,如下图形表示:
图 18.1拍卖测试流程
现在,让我们写一个执行主场景的测试:
public class AuctionTest extends TestCase {
// 解析流程定义
static ProcessDefinition auctionProcess =
ProcessDefinition.parseParResource("org/jbpm/tdd/auction.par");
// 获取节点,用于以后的断言
static StartState start = auctionProcess.getStartState();
static State auction = (State) auctionProcess.getNode("auction");
static EndState end = (EndState) auctionProcess.getNode("end");
// 流程实例
ProcessInstance processInstance;
// 执行主路径
Token token;
public void setUp() {
// 为给定的流程定义创建一个新的流程实例
processInstance = new ProcessInstance(auctionProcess);
// 执行的主路径为根令牌
token = processInstance.getRootToken();
}
public void testMainScenario() {
// 流程实例创建后,执行主路径位于开始状态
assertSame(start, token.getNode());
token.signal();
// 信号后,执行主路径转移到aution状态
assertSame(auction, token.getNode());
token.signal();
// 信号后,执行主路径移到结束状态,且流程结束
assertSame(end, token.getNode());
assertTrue(processInstance.hasEnded());
}
}
18.2 XML源
在开始写执行场景之前,你需要一个流程定义,获取一个流程定义对象最简单的方式是通过解析xml。如果你的编写环境有代码完成(code completion)功能,键入ProcessDefinition.parse并激活代码完成功能,然后你会获得多个解析方法,主要有三种方式去编写可被解析到一个ProcessDefinition对象的xml:
18.2.1 解析流程档案
流程档案是一个zip文件,它包含一个有流程XML置于其中的名为processdefinition.xml的文件,jBPM流程设计器读取和编写流程档案。例如:
...
static ProcessDefinition auctionProcess =
ProcessDefinition.parseParResource("org/jbpm/tdd/auction.par");
...
18.2.2 解析xml文件
在其它情况下,你可能手工写processdefinition.xml文件,然后使用如ant脚本方式来打包zip文件。这种情况下,你可以使用JpdlXmlReader。
...
static ProcessDefinition auctionProcess =
ProcessDefinition.parseXmlResource("org/jbpm/tdd/auction.xml");
...
18.2.3 解析xml字符串
最简单的选择是在单元测试中从一个普通字符串解析xml。
...
static ProcessDefinition auctionProcess =
ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state name='start'>" +
" <transition to='auction'/>" +
" </start-state>" +
" <state name='auction'>" +
" <transition to='end'/>" +
" </state>" +
" <end-state name='end'/>" +
"</process-definition>");
...
18.3 测试子流程
TODO(请看test/java/org/jbpm/graph/exe/ProcessStateTest.java)