2008年10月13日星期一

jBPM用户指南-CH03


第三章 指南

本指南将向你展示用JPDL构造的基本流程和管理运行时执行的API的用法。
指南的格式是解释一组的例子。这些例子将集中讲解一个详细的主题并包含一些扩展的内容。这些例子也可以在jBPM下载包中的src/java.examples目录找到。
最好的学习方式是创建一个工程,并在给出的例子的基础上改造,进行试验。

Eclipse用户入门:下载jbpm-3.0-[version].zip包并解压到你的系统中。然后按照Eclipse菜单的"File" --> "Import..." --> "Existing Project into Workspace"顺序进行操作。点击“下一步”(Next),然后浏览jBPM根目录并点击“完成”(Finish)按钮。现在在你的工作区就有了jbpm.3工程了。现在你可以在src/java.examples/...目录下发现本指南的示例。当你打开这些示例,你可以通过菜单"Run" --> "Run As..." --> "JUnit Test"来运行它们。
jBPM包括一个图形设计工具来制作例子中所示的XML文件。你可以在2.1节“Downloadables Overview”(下载概览)中找到图形设计器的下载指示。
学习本指南,你不需要具备图形设计工具。


3.1. Hello World 的例子
流程定义是由节点和转换构成的有向图。本Hello world流程由有3个节点。为了看看这些小单位如何组合在一起,我们将不利用设计器工具从一个简单的流程开始。下图展示了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>"
);

//下一行创建了一个流程定义的执行,构造之后,流程执行就有一个定位到开始状态的执行主路径(=root token)
ProcessInstance processInstance = new ProcessInstance(processDefinition);

//构造之后,流程执行就有一个定位到开始状态的执行主路径(=root token)
Token token = processInstance.getRootToken();//获取执行主路径

// 同样的,在构造完之后,执行主路径定位到流程定义的开始状态。
assertSame(processDefinition.getStartState(), token.getNode());
// 现在开始流程的执行,离开开始状态,结束默认的转换。
// 现在开始流程的执行,留下开始状态作为默认的转换。
// Let's start the process execution, leaving the start-state
// over its default transition.
token.signal();//发信号
// signal方法将阻塞直到流程执行进入一个等待(wait)状态。

// 流程执行将进入第一个等待状态‘s’。所以执行的主路径现在定位到状态‘s’。
assertSame(processDefinition.getNode("s"), token.getNode());

// 我们再送另外一个信号signal. 这将继续执行离开状态's' 结束默认的转换(transition)
// 现在我们发另一个信号。这将通过留下状态‘s’作为默认转换而挂起执行。
// Let's send another signal. This will resume execution by
// leaving the state 's' over its default transition.
token.signal();//发另一个信号

// 现在signal方法返回,因为流程实例已经到达结束状态。
assertSame(processDefinition.getNode("end"), token.getNode());
}


3.2. 数据库的例子
jBPM的一个基本特性是它能够在当流程处于等待状态时,将流程执行持久化存储到数据库中。下一个例子将向你展示如何在jBPM数据库中存储一个流程实例。例子给出一个将会发生的上下文.分开的方法因不同的用户代码块而创建。比如:一个web应用中的用户代码段开始了一个流程并将其执行持久化存储到数据库中。然后,一个消息驱动bean从数据库中装载了这个流程实例并挂起该流程的执行。
更多的jBPM持久化内容将在 第七章:持久化 找得到。

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使用的服务分开。
" <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>"
);
}

//JUnit的setUp()方法
public void setUp() {
jbpmConfiguration.createSchema();
}
//JUnit的tearDown()方法
public void tearDown() {
jbpmConfiguration.dropSchema();
}

public void testSimplePersistence() {
// 在下面调用的3个方法之间,所有的数据都通过数据库进行传输。在这个单元测试中,
// 因为我们想测试一个完整的想定的流程,所以这3个方法依次执行。但在实际情况中,
// 这些方法的展示的是对一个服务的不同请求。
// 由于我们以一个干净的,空的内存数据库开始,我们需要首先部署流程。在实际情况中,
// 这一步将由流程开发者来完成。
deployProcessDefinition();//部署流程定义ProcessDefinition

// 假设当用户在web应用中提交了一个表单时我们想启动一个流程实例(=process execution) ...
processInstanceIsCreatedWhenUserSubmitsWebappForm();
// 然后,等下异步消息到来时,执行必须继续。
theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();//当收到异步消息时流程实例继续
}

//部署流程定义ProcessDefinition
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持久化上下文构造器
// Lookup the pojo persistence context-builder that is configured above
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
// 在数据库中部署流程定义
jbpmContext.deployProcessDefinition(processDefinition);

} finally {
// 销毁pojo持久化上下文。这包括刷新插入流程定义到数据库的SQL语句。(flush后事务将提交)
jbpmContext.close();
}
}

// 当用户提交web应用Form时流程实例被创建
public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
// 本方法中的代码应该放在struts的action中或是JSF的managed bean中。
// 查找上面配置的pojo持久化上下文构造器(context-builder)
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

GraphSession graphSession = jbpmContext.getGraphSession();

ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("hello world");
// 当我们从数据库中获取了processDefinition后,我们可以像在hello world的例子(没有持久化存储)
// 一样创建这个流程定义的一个执行。
ProcessInstance processInstance = new ProcessInstance(processDefinition);

Token token = processInstance.getRootToken();
assertEquals("start", token.getNode().getName());
// 让我们来启动流程的执行
token.signal(); //发信号
// 现在流程处于状态‘s'。
assertEquals("s", token.getNode().getName());
// 现在流程实例已经被保存到数据库中。所以该流程的执行的当前状态已经被存储到数据库中
jbpmContext.save(processInstance);

// 下面的方法将从数据库取回流程并通过提供另外的信号来挂起执行。
// The method below will get the process instance back out
// of the database and resume execution by providing another
// external signal.

} finally {
// 拆卸pojo持久化上下文。
jbpmContext.close();
}
}

//当异步消息收到时流程实例继续
public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
// 该方法中的代码可以是消息驱动BEAN的内容
// 查找上面配置的pojo持久化上下文构造器
// Lookup the pojo persistence context-builder that is configured above
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

GraphSession graphSession = jbpmContext.getGraphSession();
// 首先,我们需要从数据库取回流程实例
// 这里有很多选项可以知道我们正在处理的是哪个流程。
// 这个简单测试用例最简单的地方是只要去查找完整的流程实例列表。
// 那将给我们唯一的一个结果。所以我们来查找流程定义。
ProcessDefinition processDefinition = graphSession.findLatestProcessDefinition("hello world");
// 现在我们搜索这个流程定义的所有流程实例
List processInstances = graphSession.findProcessInstances(processDefinition.getId());
// 因为我们知道该单元测试的上下文中,只有一个执行。(所以这里使用get(0))
// 在实际情况下,processInstanceId能够从到达的消息或用户作出的一个选择中抽取出来

ProcessInstance processInstance =
(ProcessInstance) processInstances.get(0);
// 现在我们可以继续执行。注意:流程实例processInstance委派信号到执行的主路径(= root token)。
processInstance.signal();//发信号
// 在发信号之后,我们知道流程的执行已到达结束状态
assertTrue(processInstance.hasEnded());
// 现在我们可以更新执行的状态到数据库中了。
jbpmContext.save(processInstance);

} finally {
// 拆卸pojo流程实例持久化上下文。
jbpmContext.close();
}
}
}




3.3. 上下文的例子:流程变量
当流程执行时,流程变量包含了上下文信息。流程变量与java.util.Map把变量名映射到值类似,都是java对象。流程变量作为流程实例的一部分持久保存。为了使事情更加简单,在这个例子中,我们将只展示操作变量的API,而不持久化保存。

更多关于变量的信息请看 第10章 Context

// 这个例子同样从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();//发信号

// 变量可以通过上下文实例访问。
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(状态节点)替换了。任务节点是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();
// 现在,我们检查一下任务实例是否真的赋给了“papa”
assertEquals("papa", taskInstance.getActorId() );
// 现在,我们假设“papa”已经完成了他的职责并标记任务为完成。
taskInstance.end();
// 由于这是要做的最后(唯一)一个任务,该任务的完成触发了流程实例执行的继续。
assertSame(processDefinition.getNode("end"), token.getNode());
}

3.5. 定制 action 的例子
Actions 是绑定你的自定义java代码到jBPM流程的一种机制。Actions能够联合组建的节点(如果他们在流程的图形化表示中相关的话)。或者actions也可以被放在诸如带走一个转换、离开一个节点或进入一个节点之类的事件中。在这种情况下,actions不是图形化表示的一部分,但当在一个运行时流程执行触发了事件的时候actions会被执行。
在我们要开始使用我们的例子前先来看看action的实现:MyActionHandler。这个action处理器实现没有真正做很多事情,它只是将boolean类型的变量isExecuted设置为true。变量isExecuted是静态的,所以它不但不只在action handler可以访问,而且从要检查它的值的其他action也可以访问。
更多关于 actions 的内容可以从 9.5节 “Actions”找到。

// MyActionHandler 描绘了一个类,该类能在jBPM流程执行期间执行一些用户代码。
public class MyActionHandler implements ActionHandler {
// 在每个测试之前(在setUp方法中),isExecuted成员变量将被设置为false。
public static boolean isExecuted = false;

// action将设置isExecuted为true,故单元测试可以显示action在何时执行。
public void execute(ExecutionContext executionContext) {
isExecuted = true;
}
}

如前所述,在每个测试之前,我们设置静态域MyActionHandler.isExecuted为false;

// 每个测试将以设置MyActionHandler的静态成员isExecuted为false开始;
public void setUp() {
MyActionHandler.isExecuted = false;
}

我们将在一个转换中开始一个action

// 测试转换Action
public void testTransitionAction() {
// 下一个流程是hello world流程的一个变量。
// 我们在从起始状态到结束状态的转换之上加了一个action。本测试的目的是展示集成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());

// 下一个信号将触发root token的执行。该token将带走转换和action,并且该action在signal方法调用期间被执行。
processInstance.signal();

// 这里我们可以看到在signal方法调用期间MyActionHandler被执行了。
assertTrue(MyActionHandler.isExecuted);
}

下一个例子展示同样的action,但现在这个action分别放于enter-node和leave-node事件中。注意:节点和转换相比有多于一个的事件类型,转换只有一个事件。因此放置于节点的action应该被放在一个事件元素中。

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’被进入因而action被执行。
processInstance.signal();
assertTrue(MyActionHandler.isExecuted);

// 现在重置MyActionHandler.isExecuted
MyActionHandler.isExecuted = false;

// 下一个信号将触发执行离开状态‘s’。所以action将再次被执行一次。
processInstance.signal();
// 看,结果就是这样.
assertTrue(MyActionHandler.isExecuted);

没有评论: