jBPM实例开发jpi.docx
jBPM实例开发1.概述本处主要将向你展示如何用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 下载概述”中找到这个工具的下载说明,但是完成本指南不需要图形化设计器工具。1.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(); 1.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(); 1.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"); 1.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(); 1.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要求J2SE1.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(从jboss4.0.3)Dom4j-1.6.1.jar流程定义和hibernate持久化。Xml解析。lib/dom4jjBPM的典型部署中将包括流程定义和流程执行的持久化,在这种情况下,jBPM除了对hibernate及其所依赖库之外不再有任何其他依赖。当然,hibernate所需要的库依赖于环境以及你将使用的特性,详细信息请查询hibernate文档。下表给出了一个普通POJO部署环境下的指示。jBPM的发布使用hibernate3.1,但是它也可以使用3.0.x,如果那样的话,你不得不在hibernate.queries.hbm.xml配置文件中更新一些hibernate查询,