第三章. 讲座

这个讲座将展示如何建造一个jpdl和如何使用API的方法来管理运行时的执行.

这个讲座的方式是解释一系列的例子. 每个例子将集中关注特别的主题和额外的说明. 这些例子可以在jBPM包的examples目录下找到.

最好的方法就是学着来建立一个Project实验所给的例子.

eclipse 用户注意:下载jbpm-3.0-[version].zip并且解压缩到系统. 然后从菜单 "File" --> "Import..." --> "Existing Project into Workspace". 点 "Next" 然后浏览jBPM 根目录然后点击 "Finish". 现在你的有了jbpm 3 project了. 你可以发现这个讲座目录在目录 src/java.examples/... . 你打开这些例子后,可以运行它们"Run" --> "Run As..." --> "JUnit Test"

jBPM 包括一个图形设计器来产生例子中的XML. 你可以从这里下载和学习有关图形设计器的说明  节 2.1, “下载一览”

3.1. Hello World 例子

一个流程 是有一个定向图(directed graph)来定义,由节点和变换组成 . hello world 流程有3个节点.如下看如何组合起来, 我们先开始一个简单的流程不使用图形设计器. 下面的图形表示hello world 流程:

The hello world process graph

Figure 3.1.  hello world 流程图

public void testHelloWorldProcess() {
          // 这个方法展现流程定义和流程执行的定义
          // 流程定义有3个节点. 
          // 一个未命名的开始状态start-state,一个状态 's' 
    // 一个结束状态名字为'end'.
           // 下面行解析一个xml text为一个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 根令牌) 此时位置在start-state处
  ProcessInstance processInstance = 
      new ProcessInstance(processDefinition);
  
          // 在构造后流程执行有一个主要的执行路径
  // (=   roottoken 根令牌) .
  Token token = processInstance.getRootToken();
  
          // 构造后, 位置处于流程定义执行路径start-state的位置
  assertSame(processDefinition.getStartState(), token.getNode());
  
        // 现在开始流程执行,离开start-state 结束默认的转换(transition) 
  token.signal();
        // 这个signal方法将会阻塞直到流程执行进入 wait 状态 

         // 流程执行在状态's' 进入第一个 等待状态
           // 因此执行主路径现在位置在 状态's' 
  assertSame(processDefinition.getNode("s"), token.getNode());

         // 我们再送另外一个信号signal. 这将继续执行离开状态's' 结束默认的转换(transition) 
  token.signal();
         // 现在信号signal方法将返回,因为流程实例到达了end-state 结束状态 
  
  assertSame(processDefinition.getNode("end"), token.getNode());
}

3.2. 数据库例子

jBPM一个基本的特性是当流程处于等待状态时候可以把流程执行 永久化到数据库中 . 下一个例子想你展示了如何存储一个流程实例到jBPM数据库中. 例子给出一个将会发生的上下文.分开的方法用来建立不同部分的用户代码. 比如一部分用户代码在web 应用程序中开始一个流程并永久化执行到数据库中.然后,message drive bean从数据库中载入流程实例并继续它的执行

jBPM 永久化的更多内容可以参看  第六章, 永久化.

public class HelloWorldDbTest extends TestCase {

            // 我们在每个应用程序中需要一个JbpmSessionFactory. 因此我们把它放在一个静态变量中 
         // JbpmSessionFactory 在test 方法中来建立一个 
       JbpmSession's.
  static JbpmSessionFactory jbpmSessionFactory = 
      JbpmSessionFactory.buildJbpmSessionFactory();
  
  static {
             // 因为HSQLDBin-memory数据库是干净的数据库,
                // 在我们开始测试前,我们需要建立table.
    // The next line creates the database tables and foreign key 
    // constraints. 
    jbpmSessionFactory.getJbpmSchema().createSchema();
  }

  public void testSimplePersistence() {
               // 在3个方法调用下面方法中间,所有数据被写入数据库 
               // 在单元测试中,这3个方法被正确执行在每个方法之后
               // 因为我们想完成测试流程场景
            // 但在实际中这些方法代表着server不同的请求 
    
               // 因为我们开始的数据库是个干净的数据库,我们需要首先发布流程在里面 
                // 在真实中,这个是由流程开发人员完成的 
    deployProcessDefinition();

            // 假定我们想开始流程实例(= 流程执行)
             // 当用户提交一个Web表单的时候.
    processInstanceIsCreatedWhenUserSubmitsWebappForm();

            // 然后,到达的异步消息将继续执行 
    theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();
  }

  public void deployProcessDefinition() {
     //定义一个流程,包括三个及点,一个未命名的start-state,一个状态's'
    //一个结束状态 end-state名字'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>
    );
    
         // 打开新的永久层会话
    JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSession();
            // 并且在永久层会话上开启事务
    jbpmSession.beginTransaction();
    
          // 保存流程定义到数据库中 
    jbpmSession
        .getGraphSession()
        .saveProcessDefinition(processDefinition);
    
      // 提交事务
    jbpmSession.commitTransaction();
       // 关闭会话.
    jbpmSession.close();
  }

  public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
           // 这个方法里的代码可以放在structs action或JSF管理bean 里 
    
         // 打开一个新的永久层会话
    JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSession();
            // 启动事务.
    jbpmSession.beginTransaction();
    
               // 查询数据库得到我们在上面步骤发布的流程定义 
    ProcessDefinition processDefinition = 
        jbpmSession
          .getGraphSession()
          .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());
    
              // 流程实例被保存在数据库 
               // 所以当前流程执行的状态被保存进数据库 
     .  
    jbpmSession
        .getGraphSession()
        .saveProcessInstance(processInstance);
    // The method below will get the process instance back out 
    // of the database and resume execution by providing another 
    // external signal.

              // web应用程序动作结束出,事务被提交.
    jbpmSession.commitTransaction();
       // 关闭jbpmSession.
    jbpmSession.close();
  }

  public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
               // 这个代码可以包含在message driven bean中.
    
         // 打开新的永久性的会话.
    JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSession();
            // 永久化会话上开始事务
               // 说明它也可能使用应用服务器的DataSource的JDBC连接 
    jbpmSession.beginTransaction();
    GraphSession graphSession = jbpmSession.getGraphSession();

    // First, we need to get the process instance back out of the database.
    // There are several options to know what process instance we are dealing 
    // with here.  The easiest in this simple test case is just to look for 
    // the full list of process instances.  That should give us only one 
    // result.  So let's look up the process definition.
    ProcessDefinition processDefinition = 
        graphSession.findLatestProcessDefinition("hello world");

              // 现在,我们搜索这个流程定义的所有流程实例.
    List processInstances = 
        graphSession.findProcessInstances(processDefinition.getId());
    
    // We know that in the context of this unit test there is 
    // only one execution.  In real life, the processInstanceId can be 
    // extracted from the content of the message that arrived or from 
    // the user making a choice.
    ProcessInstance processInstance = 
        (ProcessInstance) processInstances.get(0);
    
    // 我们可以继续执行. 说明流程实例委托信号到执行主路径(=         the root token)
    processInstance.signal();

           // 在singal后, 我们知道流程执行应该到 end-state 
    assertTrue(processInstance.hasEnded());
    
               // 现在我们可以更新执行状态到数据库中
    graphSession.saveProcessInstance(processInstance);

            // MDB结束, 事务被提交.
    jbpmSession.commitTransaction();
        // jbpmSession被关闭.
    jbpmSession.close();
  }
}

3.3. 上下文例子: 流程变量

在流程执行时候流程变量包含上下文信息. 流程变量同java.util.Map相似映射名字到值,值可能是个java对象 . 流程变量被永久化作为流程实例的一部分. 为了让事情简单,这个例子中我们仅仅展示使用变量的API而没有永久化.

关于变量的更多信息可以参看 第8章 上下文

      // 这个例子也是从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();

      // 在开始之前流程离开了start-state, 
         // 我们准备设置一些流程变量在流程实例上下文中 
     .
contextInstance.setVariable("amount", new Integer(500));
contextInstance.setVariable("reason", "i met my deadline");

        // 从现在开始,这些变量同流程实例关联 
        // 流程变量可以从用户代码中通过下面展示的API来访问 
            // 可可以在动作Action和节点的实现中访问 
        // 流程变量也作为流程实例的一部分保存进数据库  
           .

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();
  
         // 开始流程执行,完整默认的转换后离开start-state 
      .
  token.signal();
         // signal 方法将被阻塞知道流程执行进入等待状态. 
              // 在这个case中是指task-node.
  assertSame(processDefinition.getNode("t"), token.getNode());

         // 当执行到达task-node, 任务'change nappy'
  // 被建立并且NappyAssignmentHandler 被调用来决定任务将分派给睡
           //NappyAssignmentHandler 返回'papa' 

           // 在真实环境中, 任务将会从数据库中获取,通过or
         g.jbpm.db.TaskMgmtSession.
           // 因此这个例子中我们不想包括复杂的永久化 
           // 我们只是得到这个流程实例的第一个task-实例 
     (we know there is only one in this test
  // 我们知道在这个测试场景中这里只有一个).
  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. 客户化动作例子

动作action是绑定你自己的定制java代码和jBPM流程的一种机制. 动作可以同它自己的节点关联起来 (如果它们在流程图表示中相关). 动作也可以放在事件event上比如. taking a transition, leaving a node 或者 entering a node.在这个case ,动作不是图表的一部分,但是它们在流程执行产生事件的时候,也会被执行.

我们将用一个例子: MyActionHandler 来观察动作的实现.这个动作handler实现不是什么非常特别的事情.它只是设置boolean变量 isExecuted 为 true . 变量 isExecuted 是静态的因此它可以在action handler内部被访问.

关于动作action的内容可以参看  7.4节, “动作”

     // MyActionHandler 就是一个class可以在jBPM流程执行时候在某些用户代码里被执行 
public class MyActionHandler implements ActionHandler {

        // 在测试之前, isExecuted 被设置为 
       false.
  public static boolean isExecuted = false;  

         // 动作将设置true 因此 当动作被执行 
         // unit test 将会展示
  public void execute(ExecutionContext executionContext) {
    isExecuted = true;
  }
}
       // 每次测试开始都要设置MyActionHandler 的成员static isExecuted 为 
       false.
  public void setUp() {
    MyActionHandler.isExecuted = false;
  }

我们将要在转换时开始一个动作

public void testTransitionAction() {
    // The next process is a variant of the hello world process.
    // We have added an action on the transition from state 's' 
    // to the end-state.  The purpose of this test is to show 
    // how easy it is to integrate java code in a jBPM process.
    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>"
    );
    
    // Let's start a new execution for the process definition.
    ProcessInstance processInstance = 
      new ProcessInstance(processDefinition);
    
    // The next signal will cause the execution to leave the start 
    // state and enter the state 's'
    processInstance.signal();

           // 这里将显示 MyActionHandler还没有被执行 
    assertFalse(MyActionHandler.isExecuted);
    // ... and that the main path of execution is positioned in 
    // the state 's'
    assertSame(processDefinition.getNode("s"), 
               processInstance.getRootToken().getNode());
    
    // The next signal will trigger the execution of the root 
    // token.  The token will take the transition with the
    // action and the action will be executed during the  
    // call to the signal method.
    processInstance.signal();
    
    // Here we can see that MyActionHandler was executed during 
    // the call to the signal method.
    assertTrue(MyActionHandler.isExecuted);
  }

下一个例子是同样的动作,但动作被分别放在 enter-node和 leave-node 事件 .注意节点同转换相比有超过一个事件类型(event type)转换(transition)只有一个事件.

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);
// The next signal will cause the execution to leave the start 
// state and enter the state 's'.  So the state 's' is entered 
// and hence the action is executed. 
processInstance.signal();
assertTrue(MyActionHandler.isExecuted);

// Let's reset the MyActionHandler.isExecuted  
MyActionHandler.isExecuted = false;

// The next signal will trigger execution to leave the  
// state 's'.  So the action will be executed again. 
processInstance.signal();
// Voila.  
assertTrue(MyActionHandler.isExecuted);