JBPM用户指南翻译:第3章 指南

第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);
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值