这个讲座将展示如何建造一个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, “下载一览” 一个流程 是有一个定向图(directed graph)来定义,由节点和变换组成 . hello world 流程有3个节点.如下看如何组合起来, 我们先开始一个简单的流程不使用图形设计器. 下面的图形表示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()); } 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(); } } 在流程执行时候流程变量包含上下文信息. 流程变量同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")); 在下个例子里我们将要展示你怎么才能分派一个任务给一个用户.因为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()); } 动作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); |
|