JBPM开发指南

zsurelic

贡献于2011-01-20

字数:58799 关键词: 工作流引擎 jBPM

 JBPM 工作流开发指南 v1.0 JBPM开发指南 BSTEK 2007年7月 目录 第页 共107页 JBPM 工作流开发指南 v1.0 一、概述 4 二、第一个流程 5 2.1、开始前的准备 5 2.2 、Hello World 例子 6 三、学习JPDL 11 3.1 、简介 11 3.2 、流程版本(Version) 11 3.3 、流程定义 12 3.3.1 process-definition(流程定义) 12 3.3.2 node(自动节点) 13 3.3.3 start-state(开始状态) 13 3.3.4 end-state(结束节点) 14 3.3.5 state(状态) 14 3.3.6 task-node (任务节点) 15 3.3.7 fork(分支) 16 3.3.8 join(联合) 17 3.3.9 decision(决策) 18 3.3.10 transition(转换) 19 3.3.11 event(事件) 20 3.3.12 action(动作) 20 3.3.13 script(脚本) 22 3.3.14 expression(表达式) 23 3.3.15 variable(变量) 23 3.3.16 handler(句柄) 23 3.3.17 timer(定时器) 24 3.3.18 create-timer(创建定时器) 25 3.3.19 cancel-timer(取消定时器) 26 3.3.20 task(任务) 26 3.3.21 swimlane(泳道) 27 3.3.22 assignment(委派) 28 3.3.23 controller(控制器) 29 3.3.24 process-state 子流程 29 3.3.25 sub-process 子流程 30 3.3.26 condition 条件 30 3.3.27 exception-handler 异常处理 31 小结 33 四、流程中任务的分配 37 4.1 assignment-handler方式的任务分配 38 4.2 swimlane方式的任务分配 39 五、JBPM持久化 42 5.1 特殊数据库支持 42 5.2 JBPM数据库的安装 42 5.2 JBPM流程发布 45 第页 共107页 JBPM 工作流开发指南 v1.0 5.2.1 搭建JBPM的WEB应用 46 5.2.2 发布第一个流程 48 六、日历(Scheduler) 54 6.1 Scheduler在C/S程序上的应用 54 6.2 Scheduler 在Web上的应用 57 6.3 Scheduler时间的分类 60 七、异步执行 63 八、JBPM流程建模与应用 64 8.1 JBPM的建模工具 64 8.1.1 建模工具的安装 64 8.2 公司报销流程示例 67 8.2.1 流程建模 67 8.2.2 流程数据库搭建 76 8.2.3 构建业务表 79 8.2.4 搭建工程 82 8.2.5 报销流程的发布 83 8.2.6 应用程序搭建 84 九、写在最后 100 一、概述 JBPM是一个扩展性很强的工作流系统,百分百用JAVA语言开发,持久层采用Hibernate实现,理论上说,只要Hibernate支持的数据库JBPM都支持。同时它还能被部署在任何一款JAVA应用服务器上。 二、第一个流程 2.1、开始前的准备 JBPM的工程文件,大家可以到如下网站上去下载: 第页 共107页 JBPM 工作流开发指南 v1.0 http://www.jboss.com/products/jbpm 目前的最新版本是3.2.1,本文就以此版本为例。在这里请大家下载jbpm-starters-kit-3.1.2这样一个版本。在这个版本里包括一个JBPM流程设计器的Eclipse插件,和一个用JBOSS作为服务器的示例流程等相关文件。解压jbpm-starters-kit-3.1.2.rar到某个特定目录,这里我们首先用到的是包里的JBPM目录下的文件。 JBPM目录里面是JBPM的Eclipse的工程文件,我们可以用Eclipse导入该工程。从Eclipse的File菜单里选择import——> Existing Projects into Workspace——>next…根据向导找到前面提到的JBPM目录就可以把该工程导入到Eclipse当中。如下图: 好了,接下来,我们就在这个工程的基础之上来开始我们的第一个流程。 2.2 、Hello World 例子 我们的第一个流程示例源自JBPM的reference。流程图如下: 第页 共107页 JBPM 工作流开发指南 v1.0 JBPM的流程定义采用XML的方式(实际绝大多数的流程引擎的流程定义都采用的是这种方式),作为测试XML定义我们既可以写在代码当中,也可以以一个独立的XML文件的形式存在,接下来的例子我们将分别为大家介绍一下这两种情况。我们首先来看看把XML流程定义写在代码中的方式。 新建一个Junit的测试用例,测试代码如下: package org.jbpm.tutorial.helloworld; import junit.framework.TestCase; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.graph.exe.Token; public class HelloWorldTest extends TestCase { public void testHelloWorldProcess() { /* 这个段测试方法演示了一个流程的在代码中以字符串形式定义和这个流程定义的具体执行。 这个流程定义包含三个节点:一个未命名的开始状态(start-state), 一个名字为's'的状态(state)和一个名字为'end'的结束状态(end-state)。 下一行的功能是把一段xml文本解析为一个ProcessDefinition, 一个ProcessDefinition是一个java对象的形式对流程的正式的描述。 */ ProcessDefinition processDefinition = ProcessDefinition.parseXmlString( 第页 共107页 JBPM 工作流开发指南 v1.0 "" + " " + " " + " " + " " + " " + " " + " " + "" ); /* 下边的一行根据流程定义构造了的一个具体的执行实例。 构造以后,执行的流程就有了一个被定位在开始状态(start-state)上的主要的执行路径 */ ProcessInstance processInstance = new ProcessInstance(processDefinition); /* 构造以后,执行的流程就有了一个主要的执行路径(root token) */ Token token = processInstance.getRootToken(); /* 当然,构造以后,流程定义的主要的执行路径被定位在开始状态(start-state) */ assertSame(processDefinition.getStartState(), token.getNode()); /* 开始流程执行,通过默认的转换(transition)离开开始状态(start-state) */ token.signal(); 第页 共107页 JBPM 工作流开发指南 v1.0 /* 直到运行的流程进入一个等待状态,signal方法将一直被阻塞,运行的流程将要进入第一个等待状态:状态‘s’.因此现在主要的执行路径,定位到了状态‘s’上。 */ assertSame(processDefinition.getNode("s"), token.getNode()); /* 执行signal,流程将继续执行,将通过默认的转换(transition)离开状态‘s’ */ token.signal(); /* 流程实例已经到达了结束状态。 */ assertSame(processDefinition.getNode("end"), token.getNode()); } 运行测试,我们看到流程和我们预想的结果完全符合。 在这里我们的流程定义是写在一个代码中,XML的定义方式是以通过拼字符串的方式完成的,这种方式给我们带来的结果是不直观,同时流程定义起来也很不方便。除了这种定义方式之外我们可以把刚才那段写在代码里的流程定义信息搬到我们的XML文件里,同样可以达到相同的效果。接下来我们就来看一下这种做法。 流程定义文件:helloWorld.xml 第页 共107页 JBPM 工作流开发指南 v1.0 测试代码:HelloWorldTest.java package org.jbpm.tutorial.helloworld; import junit.framework.TestCase; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.graph.exe.Token; public class HelloWorldTest extends TestCase { public void testHelloWorldProcess() { ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource("helloWorld.xml"); /* 从这里可以看出,与上面那段代码唯一不同之处就是没有字符串形式的流程定义信息了,取而代之的是对流程定义的XML进行解析 */ ProcessInstance processInstance = new ProcessInstance(processDefinition); Token token = processInstance.getRootToken(); assertSame(processDefinition.getStartState(), token.getNode()); token.signal(); assertSame(processDefinition.getNode("s"), token.getNode()); token.signal(); assertSame(processDefinition.getNode("end"), token.getNode()); } 运行测试,得到的结果同上例完全相同。 到这里为止,我们已经做了一个非常简单的流程示例,对JBPM的流程定义及使用方法也有了初步的概念,在下面的内容中我们将着重来讨论 第页 共107页 JBPM 工作流开发指南 v1.0 JBPM的流程定义方法,及各个节点的主要含义及使用方法。 三、学习JPDL 3.1 、简介 JPDL(JBPM Process Definition Language)是JBPM流程定义语言。JPDL详细定义了这个状态图的每个部分,如: 开始、结束状态,状态之间的转换等。这种语言的定义对于用户来说比较容易理解,也比较容易对其进行扩展。 一个JBPM的流程定义XML文件中包含一个< process-definition>元素,而一个< process-definition>元素又包含零个或一个< description>元素,零个或多个的< swimlane>元素,一个< start-state>元素,零个或多个的< state>元素或< decision>元素或< fork>元素或< join>元素,以及零个或多个的< action>元素,零个或多个元素,一个< end-state>元素等等。此外,< process definition>元素有一个标示符,以“name”属性来表示,这个属性必须存在,用来表示该流程的名称。 3.2 、流程版本(Version) 我们的流程XML文件定义完成之后,接下来的工作就是要将其发布到对应的数据库中,当我们每次将我们的流程定义部署到数据库时,部署时流程的名称就是前面提高的里定义的name”属性的值。 JBPM的版本机制允许在数据库中多个同名流程定义共存,流程实例以当时的最新版本来启动,并且在它的整个生命周期中将保持以相同的流程定义执行。当一个新的版本被部署,新的流程实例以新版本启动,而老的流程实例则以老的流程定义继续执行。 在部署的时候,jbpm 安排一个版本(version)号码(数字)给流程定义。为了实现安排version号码,如果它是第一个版本(version),JBPM采取1+或者1。从ProcessDefinition pd= 第页 共107页 JBPM 工作流开发指南 v1.0 JbpmContext.getGraphSession() .findLatestProcessDefinition("processName") 中可以通过一个给定的processName查找最近的流程定义,这里的processName就是前面我们在定义流程的时候在里定义的name”属性的值,这个属性就是用来表示该流程的名称。如我们的下列代码就是要列出JBPM数据库里的有所有最后一次发布的流程定义的版本: JbpmContext context=JbpmContext.getCurrentJbpmContext(); List ls=context.getGraphSession().findLatestProcessDefinitions(); /* 这里返回的List是ProcessDefinition的集合 */ 3.3 、流程定义 3.3.1 process-definition(流程定义) 流程定义的根节点,是所有节点的父节点 名称 类型 数量 描述 name 属性 可选的 流程的名称。 swimlane 元素 [0..*] 流程中使用的泳道。泳道表示流程角色,它们被用于任务分配。 start-state 元素 [0..1] 流程起始状态。注意,没有起始状态的流程是合法的,但是不能被执行。 end-state|state|node|task-node|process-state|super-state|fork|join|decision 元素 [0..*] 流程定义的节点。注意,没有节点的流程是合法的,但是不能被执行。 event 元素 [0..*] 作为一个容器服务于动作的流程事件。 action|script|create-timer|cancel-timer 元素 [0..*] 全局定义的的动作,可以在事件和转换中引用。注意,为了被引用,这些动作必须指定名称。 第页 共107页 JBPM 工作流开发指南 v1.0 task 元素 [0..*] 全局定义的任务,可以在动作中使用。 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程定义中的委托类所抛出的所有异常。 3.3.2 node(自动节点) 这种节点和State相反,也称自动节点。当业务程序实例执行到这个节点,不会停止执行。而是会继续往下执行。如果该节点存在多个离开转向。那么,就会执行其中的第一个离开转向,在Node状态中,不需要外部参与者的参与,业务流程的这个部分是自动的、即时完成的。 名称 类型 数量 描述 action|script|create-timer|cancel-timer 事件 1 用于表示这个节点行为的定制动作。 普通节点元素     请参考普通节点元素。   3.3.3 start-state(开始状态) start-state是我们整个流程的开始节点,所有的流程实例从这里开始。 名称 类型 数量 描述 Name 属性 可选的 节点的名称。 Task 元素 [0..1] 起始一个流程实例的任务,或者用来捕获流程发起者 Event 元素 [0..*] 支持的事件类型:{node-leave}。 transition 元素 [0..*] 离开转换,每个离开节点的转换必须有一个不同的名称。 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。   第页 共107页 JBPM 工作流开发指南 v1.0 3.3.4 end-state(结束节点) 对于每一个流程定义都会有一个结束节点,与开始节点对应 名称 类型 数量 描述 Name 属性 必需的 结束状态的名称。 event 元素 [0..*] 支持的事件类型:{node-enter}。 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。   3.3.5 state(状态) State节点也叫手工节点,进入到这种节点,整个流程的执行就会中断。直到系统外参与者发起继续执行的命令,即调用signal或end方法,业务程序实例的执行才能够继续下去。 名称 类型 数量 描述 name 属性 必需的 节点的名称。 async 属性 {true|false},默认是false 如果设置为true,这个节点将会异步执行。请参考”异步执行”章节。 transition 元素 [0..*] 离开转换。每个离开节点的转换必须有一个不同的名称,最多只允许所有离开转换中的一个没有名称。第一个转换被指定为默认转换,当离开节点而没有指定转换时,默认转换发生。 event 元素 [0..*] 支持的事件类型:{node-enter|node-leave}。 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 timer 元素 [0..*] 指定一个定时器,用来监视节点中的一个执行所持续的时间。   第页 共107页 JBPM 工作流开发指南 v1.0 3.3.6 task-node (任务节点) 其性质和node节点一样,在没有task的时候,也都是自动执行,不等待。task-node被归类为一个等待节点,是指在task-node中的task列表中的task没有全部执行完之前,它会一直等待。Task可以在task-node节点下定义,也可以挂在process-definition节点下。最普遍的方式是在task-node节点下定义一个或多个任务。默认情况下,流程在task-node节点会处于等待状态,直到所有的任务被执行完毕。Task的执行是按顺序执行的,任务都完成后,token仍然不会指向后面的节点;需要自己手动调用processInstance.signal()才会驱动流程到下面的节点。 名称 类型 数量 描述 signal 属性 可选的 {unsynchronized|never|first|first-wait|last|last-wait},默认是last。signal指定了任务的完成对流程执行继续的影响。 create-tasks 属性 可选的 {yes|no|true|false},默认是true。当需要在运行时通过计算来决定哪个任务将被创建时,可以设置为false,如果这样的话,在node-enter事件上加一个动作,在动作中创建任务,并且把create-tasks设置为false。 end-tasks 属性 可选的 {yes|no|true|false},默认是false。如果设置end-tasks为true,在离开节点时,所有打开的任务将被结束。 task 元素 [0..*] 当执行到达本节点时所应被创建的任务。 普通节点元素     请参考普通节点元素。   为了帮助读者理解task-node节点的signal属性,这里举例如下: 对于这样的流程定义: 第页 共107页 JBPM 工作流开发指南 v1.0 a) 这里没有定义signal属性的值,这就表明当节点中的三个任务都完成后,流程才进入后面的节点 b) 当表明token不会在本节点停留,而是直接到后面的节点 c) 当表明三个任务都完成后,token仍然不会指向后面的节点;需要自己手动调用processInstance.signal()才会驱动流程到下面的节点 d) 当表明只要有一个任务完成后,token就指向后面的节点 e) 当表明当第一个任务实例完成时继续执行;当在a节点入口处没有任务创建时,token在a任务节点处等待,直到任务被创建或完成。 f) 当时,这是默认值,和不设置signal属性的情况相同。 g) 当时,当最后一个任务实例完成时候继续执行下去。 当a这个任务节点没有任务被建立时,任务节点等待直到任务被建立。 3.3.7 fork(分支) 一个fork把一个执行路线分割成多个执行路线. 默认分支的行为是为每个离开分支转换建立一个子令牌,在令牌要到达的分支之间建立一个父母-子女关系 名称 类型 数量 描述 name 属性 必需的 节点的名称。 第页 共107页 JBPM 工作流开发指南 v1.0 async 属性 {true|false},默认是false 如果设置为true,这个节点将会异步执行。请参考”异步执行”章节。 transition 元素 [0..*] 离开转换。每个离开节点的转换必须有一个不同的名称,最多只允许所有离开转换中的一个没有名称。第一个转换被指定为默认转换,当离开节点而没有指定转换时,默认转换发生。 event 元素 [0..*] 支持的事件类型:{node-enter|node-leave}。 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 timer 元素 [0..*] 指定一个定时器,用来监视节点中的一个执行所持续的时间。 3.3.8 join(联合) 默认联合(join)假设所有来自同一个父母的子令牌联合,当在上使用fork(分支)这个情形就出现了并且所有令牌分支建立,并且到达同一个联合(join)。当全部令牌都进入联合的时候联合就结束了, 然后联合将检查父母-子女, 当所有兄弟令牌到达联合(join),父母令牌将传播(唯一的)离开转换,当还有兄弟令牌活动时,联合的行为将作为等待状态。 名称 类型 数量 描述 name 属性 必需的 节点的名称。 async 属性 {true|false},默认是false 如果设置为true,这个节点将会异步执行。 transition 元素 [0..*] 离开转换。每个离开节点的转换必须有一个不同的名称,最多只允许所有离开转换中的一个没有名称。第一个转换被指定为默认转换,当离开节点而没有指定转换时,默认转换发生。 event 元素 [0..*] 支持的事件类型:{node-enter|node-leave}。 第页 共107页 JBPM 工作流开发指南 v1.0 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 timer 元素 [0..*] 指定一个定时器,用来监视节点中的一个执行所持续的时间。 对于Join节点,我们知道默认是要等到所有分支都到了流程才能往下继续走,要改变这一情况,我们可以通过给该节点加Action的方法改变该Join节点的Discriminator,就可以使只要有一个分支到达流程就可以继续执行的效果了,如下面的Action: package workflow.test.action; //这里通过设置Discriminator可以实现只要有一个分支到达流程就可以继续了, //它的默认值是false import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.node.Join; public class JoinAction implements ActionHandler{ public void execute(ExecutionContext arg0) throws Exception { Join join=(Join)arg0.getNode(); join.setDiscriminator(true); } } 3.3.9 decision(决策) 一个decision用以决定在多个执行路径中哪个才可以被执行。如果你是一个程序员,把它可以理解成switch case结构即可,一个decision能够具有许多离开的transition。 名称 类型 数量 描述 handler 元素 要么指定“handler”元素,或者在转换上指定条件。 一个org.jbpm.jpdl.Def.DecisionHandler的实现名称。 transition 元素 [0..*] 离开转换。决策的离开转换可以被扩展为拥有一个条件,决策会查找条件计算为 第页 共107页 JBPM 工作流开发指南 v1.0 true的第一个转换,没有条件的转换被认为计算为true(为了建模“otherwise”分支)。请参考condition元素。 普通节点元素     请参考普通节点元素。   Handler所指定的DecisionHandler的实现类里的decide方法返回一个字符串,表示要执行哪个transition,如下例: package workflow.qingjia.shenpi.common; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.node.DecisionHandler; public class ForkDecision implements DecisionHandler{ public String decide(ExecutionContext arg0) throws Exception { String dayCount=(String)arg0.getVariable("dayCount"); String go="to boss approve"; if(Integer.parseInt(dayCount)>10){ go="to join"; } return go; } } 3.3.10 transition(转换) 转换用来指定节点之间的连接。transition元素放在node里面,那么这个transition就会从这个节点出离开。 名称 类型 数量 描述 name 属性 可选的 转换的名称。注意,每个节点的离开转换必须有一个不同的名称。 第页 共107页 JBPM 工作流开发指南 v1.0 to 属性 必需的 目标节点的分级名称,表示将要达到的那个节点名称. action|script |create-timer |cancel-timer 元素 [0..*] 发生转换时将要执行的动作。注意,转换的动作无需放入事件(因为只有一个事件)。 exception-handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。   3.3.11 event(事件) JBPM定义了一系列与工作流节点元素相关联的事件,例如,流程实例运行过程中,可以触发节点进入(node-enter)、节点离开 (node-leave)、流程启动(process-start)、流程结束(process-end)、任务创建(task-create)、 任务分派(task-assign)、任务启动(task-start)等事件。 在流程定义时,JBPM的事件均与action绑定。事件的触发将导致相应actions的执行。 名称 类型 数量 描述 type 属性 必需的 表示相对于事件要放置的元素事件类型。 action|script|create-timer| cancel-timer 元素 [0..*] 在这个事件上将要执行的动作列表。   3.3.12 action(动作) 一个action是一段java代码。在流程执行期间在一些事件之上定义,这样会在相关事件触发时自动在工作流引擎上执行。 名称 类型 数量 描述 name 属性 必需的 动作的名称。当动作被指定名称后,它们可以在流程定义中被查出,这对于运行时动作以及仅一次声明动作是有用的。 第页 共107页 JBPM 工作流开发指南 v1.0 class 属性 或者用ref-name,或者用expression。 实现org.jbpm.graph.def.ActionHandler接口的类的全名。 ref-name 属性 或者用class。 所引用动作的名称。如果指定一个引用动作,则本动作不需要再做处理。 expression 属性 或者指定一个class,或者ref-name。 一个解决一个方法的jPDL表达式。 accept- propagated-events 属性 可选的 {yes|no|true|false},默认是yes|true。如果设置为false,则动作仅在本动作元素的触发事件上被执行。更多信息,请参考“第9.5.4 事件传播”。 config-type 属性 可选的 {field|bean|constructor|configuration-property}。指定动作对象将被怎样创建以及本元素的内容怎样象配置信息那样被动作对象所使用。 async 属性 {true|false} 默认false,这意味着动作将在当前执行的线程中被执行。如果设置为true,一个消息将被发送到命令执行器,并且执行器组件将在一个独立的事务中同步执行动作。请参考”异步执行”章节。   {内容} 可选的 action的内容可以被作为你定制动作实现的配置信息,这是考虑到可重用的委托类的创建。有关委托配置的更多信息,请参考“第16.2.3节委托配置”。   3.3.13 script(脚本) Script里是动作执行的beanshell脚本. 更多有关beanshell的的信息请参考Beanshell 第页 共107页 JBPM 工作流开发指南 v1.0 的网站: http://www.beanshell.org 。如下所示 ... 表格 16.16 名称 类型 数量 描述 name 属性 可选的 脚本动作的名称。当动作被指定名称后,它们可以在流程定义中被查出,这对于运行时动作以及仅一次声明动作是有用的。 Accept -propagated -events 属性 可选的[0..*] {yes|no|true|false},默认是yes|true。如果设置为false,则动作仅在本动作元素的触发事件上被执行. expression 元素 [0..1] beanshell脚本。如果你没有指定variable元素,可以写表达式作为脚本元素的内容(忽略expression元素标签)。 variable 元素 [0..*] 脚本所需变量。如果没有指定变量,则当前令牌的所有变量将被装载到脚本,当你想要限制装载到脚本中的变量数量时使用variable。   3.3.14 expression(表达式) Expression里可书写Beanshell脚本 名称 类型 数量 描述 第页 共107页 JBPM 工作流开发指南 v1.0   {内容}   一个beanshell脚本。   3.3.15 variable(变量) 一个是变量是一种key-value对。它与过程实例(一次过程执行)相关联。Key是java.lang.string,value是任何java类型的任何pojo。所以任何是java类型,即使不给jbpm知道也能被应用到变量中。JBPM的流程变量在尽量模仿java.util.map的语义。这一点可以通过JBPM的API来了解。也就是说一个变量只能当它被插入时被赋值,任何java类型都可以作为变量中的value。 名称 类型 数量 描述 name 属性 必需的 流程变量的名称。 access 属性 可选的 默认是read,write,用逗号分割的一个访问列表。迄今为止,使用的访问仅为read,write和required。 mapped-name 属性 可选的 默认是变量的名称。用来指定变量名称被映射的名称,mapped-name的含义依赖于这个元素所被使用的上下文。对于一个脚本,将是一个脚本变量名称;对于一个任务控制器,将是任务表单参数的标签;对于一个process-state,将是在子流程中使用的变量名称。   3.3.16 handler(句柄) Handler是在定义一个decision时需要为其定义一个DecisionHandler时采用。 名称 类型 数量 描述 expression 属性 或者用class 一个jPDL表达式,返回结果被用toString()方法转换为字符串,结果字符串应该与某个离开转换匹配。 第页 共107页 JBPM 工作流开发指南 v1.0 class 属性 或者用ref-name 实现了org.jbpm.graph.node.DecisionHandler接口的类的全名。 Config -type 属性 可选的 {field|bean|constructor|configuration-property}。指定动作对象将被怎样创建以及本元素的内容怎样象配置信息那样被动作对象所使用。   {内容} 可选的 Action里的内容可以用来帮助结合我们的业务来处理我们的流程,同时我们可以在Action里加上业务处理逻辑,以更好的利用流程.   3.3.17 timer(定时器) 定时器timer可以被用于decision fork join node process-state state super-state task-node,可以设置开始时间duedate和频率repeat,定时器动作可以是所支持的任何动作元素,如action或script。 timer 还有一个很重要的属性cancel-event,这个是timer和task结合时使用的,任务定时器的cancel-event可以被定制。默认情况 下,当任务被结束时(=完成)任务上的定时器将被取消,这是通过在定时器上使用cancel-event属性,流程开发者可以定制诸如task- assign或task-start。cancel-event支持多个事件,通过在属性中指定一个用逗号分割的列表,可以组合cancel-event 的类型。 名称 类型 数量 描述 name 属性 可选的 定时器的名称。如果没有指定名称,则采用外部的节点名称。注意,每个定时器应该有一个唯一的名称。 duedate 属性 必需的 所指定的定时器创建到定时器执行之间的期限(可以用业务时间来表示)。 repeat 属性 可选的 {duration|yes|true}当一个定时器在预期时间执行后, 第页 共107页 JBPM 工作流开发指南 v1.0 “repeat”可选项指定了在离开节点之前重复的执行定时器之间的期限。如果指定为true或false,则与duedate相同的期限被使用。 transition 属性 可选的 当定时器执行、定时器事件触发后以及执行动作时时所使用的转换名称。 cancel-event 属性 可选的 这个属性只用在任务的定时器中,它指定了定时器将被取消的事件。默认是task-end事件,但是也可以被设置为如task-assign或task-start。cancel-event的类型也可以通过指定一个用逗号分割的列表被组合。 action|script| create-timer| cancel-timer 元素 [0..*] 当定时器被触发时所应被执行的动作。   3.3.18 create-timer(创建定时器) Create-timer是定时器的创建 名称 类型 数量 描述 name 属性 可选的 定时器的名称。这个名称可被用于用一个cancel-timer动作取消定时器。 duedate 属性 必需的 所指定的定时器创建到定时器执行之间的期限(可以用业务时间来表示)。请参考“第14.1节期限”中的语法。 repeat 属性 可选的 {duration|’yes’|’true’}当一个定时器在预期时间执行后,“repeat”可选项指定了在离开节点之前重复的执行定时器之间的期限。如果指定为true或yese,则与duedate相同的期限被使用。请参考“第14.1节期限”的语法。 transition 属性 可选的 当定时器执行、定时器事件触发后以及执行动作时时(如果要)所获取的转换名称。   第页 共107页 JBPM 工作流开发指南 v1.0 3.3.19 cancel-timer(取消定时器) Cancel-timer是定时器的取消 名称 类型 数量 描述 name 属性 可选的 要被取消的定时器的名称。   3.3.20 task(任务) Task 是是流程定义里的一部分,它决定了task instance的创建和分配 名称 类型 数量 描述 name 属性 可选的 任务的名称。命名的任可以被引用并且可以通过TaskMgmtDefinition被查出。 blocking 属性 可选的 {yes|no|true|false} 如果blocking设置为true,当任务没有结束时节点不能被离开(必须要通过taskInstance.end()方法离开节点);如果设置为false(默认),允许用户通过signal继续执行和离开节点。默认设置为false,因为通常是由用户接口来强制阻塞。 signalling 属性 可选的 {yes|no|true|false},默认是true。如果设置signalling为false,则任务没有触发令牌继续的能力。 duedate 属性 可选的 延迟时间(任务执行的的延迟时间)。请见业务日历中的解释。 swimlane 属性 可选的 引用一个swimlane,如果在任务上指定了一个swimlane,则assignment将被忽略。 priority 属性 可选的 {highest,high,normal,low,lowest}之一。作为选择,可以为priority指定任何整数,供参考:(highest=1,lowest=5)。 assignment 元素 可选的 描写一个委托,该委托将在任务被创建时把任务分配给一个参与者。 event 元素 [0..*] 支持的事件类型:{ 第页 共107页 JBPM 工作流开发指南 v1.0 task-create|task-start|task-assign|task-end}。为了任务分配,我们特别的为TaskInstance添加了一个非持久化的属性previousActorId。 exception -handler 元素 [0..*] 一个异常处理器列表,用于这个流程节点中的委托类所抛出的所有异常。 timer 元素 [0..*] 指定一个监视本任务执行期限的一个定时器。对于任务定时器特殊的是可以指定cancel-event,cancel-event默认是task-end,但是它可以被自定义如task-assign或task-start。 controller 元素 [0..1] 指定流程变量怎样被转换为任务表单参数。任务表单参数有用户界面使用,用力向用户表现一个任务表单。   3.3.21 swimlane(泳道) 实际应用中,一个人是一个流程中多个Task的参与者(actor)的情况是很常见的。在jbpm中通过创建一个swimlane并且把swimlane赋给一个task的方式来设置当前task的参与者(actor)。一个业务流程中的swimlane可以被看做为一个参与者的参与者对象的名称,当然它不一定是固定的某个人,它可以是一个用户组,一个特定用户的角色等。首次执行到达一个Task,赋给该Task的一个swimlane就会算出参与者(actor)。 名称 类型 数量 描述 name 属性 必需的 泳道的名称。泳道可以被引用并且可以通过TaskMgmtDefinition被查出。 assignment 元素 [1..1] 指定泳道的分配。这个分配在本泳道中的第一个任务实例被创建时完成。   3.3.22 assignment(委派) 当流程执行到某个Task的时候,引时流程引挚要调用相应的swimlane或assignment将当前的 第页 共107页 JBPM 工作流开发指南 v1.0 task分配(委派)给某个参与者,外部参与者可以是一个人也可以是某个系统等。 名称 类型 数量 描述 expression 属性 可选的 由于历史原因,这个属性的表达式不是jPDL表达式,而是对jBPM身份组件的一个分配表达式。 actor-id 属性 可选的 一个actorId,可以与pooled-actors协同使用。actor-id被作为一个表达式,因此你可以引用一个固定的actorId,如actor-id=”bobthebuiler”;或者你可以引用一个可以返回一个字符串的属性或方法,如actor-id=”myVar.actorId”,这将调用任务实例变量“myVar”上的getActorId方法。 Pooled -actors 属性 可选的 一个逗号分割的actorId列表,可以与actor-id协同使用。一个固定的参与者池可以指定如下:pooled-actors=”chicagobulls,pointersisters”。 pooled-actors被作为一个表达式,因此你可以引用一个返回String[]、Collection、或一个逗号分割的池中的参与者列表的属性或方法。 class 属性 可选的 一个实现org.jbpm.taskmgmt.def.AssignmentHandler接口的类的全名称。 config-type 属性 可选的 {field|bean|constructor|configuration-property}。指定分配处理器对象(assignment-handler-object)对象将被怎样创建以及本元素的内容怎样象配置信息那样被分配处理器对象所使用。   {内容} 可选的 assignment元素的内容可以被作为分配处理器(AssignmentHandler)实现的配置信息,这是考虑到可重用的委托类的创建。   3.3.23 controller(控制器) 在任务执行时,可能需要读、写流程变量;在任务完成并提交时,可能需要写流程变量。为此,jBPM提供了"任务变量"的概念。在某些情况下,任务变量和流 程变量并非简单的一一对应关系,例如,三个流程变量代表三个月的销售额,任务变量只需要它们的平均值。为实现任务与流程实例之间的信息交流, 第页 共107页 JBPM 工作流开发指南 v1.0 jBPM设置 了任务控制器机制。该机制也采用递进模式:首先,jBPM提供基本(默认)的任务控制器;如果不敷使用,二次开发人员可以使用自定义的任务控制器。 jBPM的任务控制器机制在流程变量和任务变量之间架起了一座桥梁。 名称 类型 数量 描述 class 属性 可选的 一个实现org.jbpm.taskmgmt.def.TaskControllerHandler接口的类的全名称。 Config -type 属性 可选的 {field|bean|constructor|configuration-property}。指定分配处理器对象(assignment-handler-object)对象将被怎样创建以及本元素的内容怎样象配置信息那样被分配处理器对象所使用。   {内容}   controller元素的内容要么是指定的任务控制处理器的配置信息(如果指定了class属性),要么必须是一个variable元素列表(如果没有指定任务控制器)。 variable 元素 [0..*] 如果没有通过class属性指定任务控制处理器,则controller元素的内容必须是变量列表。   3.3.24 process-state 子流程 process-state是JBPM提供的用来处理子流程的节点,一个process-state只能对应一个子流程,究竟指到哪个子流程可以在process-state的action里指定,当token执行到指定的子流程时,子流程就已经启动,不用像启动主流程一样手工启动子流程。其它部分的处理就和普通的流程没有区别了。 名称 类型 数量 描述 name 属性 必需的 名称。 第页 共107页 JBPM 工作流开发指南 v1.0 Sub-process 元素 只能定义一个 子流程 variable 变量 [0…*] Variable是用来指定如何把数据从父流程copy到子流程   3.3.25 sub-process 子流程 名称 类型 数量 描述  name 属性 必需的  子流程的名称 version 属性 可选 子流程的版本。如果没有指定该属性,默认将会采且该子流程的最后一个版本 3.3.26 condition 条件 名称 类型 数量 描述   {内容}或属性 表达式 必需的  condition元素的内容是一个计算结果为布尔值的jPDL表达式。决策采用第一个表达式处理结果为true的转换(按在processdefinition.xml中的顺序),如果没有条件处理结果为true,则采用默认离开转换(也就是第一个)。   3.3.27 exception-handler 异常处理 Jbpm的异常处理机制仅仅集中于java异常,流程定义本身的执行不会导致什么异常,只有在执行委托类时才会导致异常。 在流程定义(process-definitions)添加的exception-handler对整个流程起作用、节点( 第页 共107页 JBPM 工作流开发指南 v1.0 nodes)上添加异常只对当前的节点起作用(同时如果在process-definitions里也设置了exception-handler那么将不会再执行process-definitions里的exception-handler),和转换(transitions)添加exception-handler只对当前的transitions起作用(同时如果在process-definitions里也设置了exception-handler那么将不会再执行process-definitions里的exception-handler),可以指定一个异常处理(exception-handlers)清单,每个异常处理(exception-handler)有一个动作列表,当在委托类中发生异常时,会在流程元素的父层次搜索一个适当的异常处理(exception-handler),当它被搜索到,则异常处理(exception-handler)的动作将被执行。 注意,Jbpm的异常处理机制与java异常处理不完全相似。在java中,一个捕获的异常可以影响控制流,而在Jbpm中,流程不会被Jbpm异常处理机制所改变。异常要么被捕获,要么不捕获,没有被捕获的异常被抛向客户端(例如客户端调用token.signal()),而被捕获的异常则是通过Jbpm的exception-handler,对于被捕获的异常,图执行仍会继续,就像没有异常发生一样。 在处理异常的动作中,可以使用Token.setNode(Node node)把令牌放入图中的任何节点。 名称 类型 数量 描述 exception-class 属性 可选的 指定与本异常处理器所匹配的java throwable类,如果这个没有指定这个属性,则它匹配所有异常(java.lang.Throwable)。 action 元素 [1..*] 当异常被异常处理器捕获时将要执行的动作列表。 定义示例: 第页 共107页 JBPM 工作流开发指南 v1.0 ProcessException类的代码如下: package gj.action; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; public class ProcessException implements ActionHandler{ 第页 共107页 JBPM 工作流开发指南 v1.0 public void execute(ExecutionContext executionContext) throws Exception { String errorMsg=executionContext.getException().getMessage(); System.out.println("异常类型"+executionContext.getException().toString()+" 异常消息:"+errorMsg); } } 这个类就可以用来处理整个ProcessInstance中发生的异常。其它在各种类型node里和在transition里定义的exception-handler的处理方式类似,只不过其作用范围仅限制为当前的node或transition。  小结 看到这里,我们已经对JPDL的流程定义语言有了较深的理解,接下来我们可以自己动手写一些流程定义的文件,以此加深对JPDL的理解。我们来看一下下面的流程定义文件内容: 第页 共107页 JBPM 工作流开发指南 v1.0 第页 共107页 JBPM 工作流开发指南 v1.0 第页 共107页 JBPM 工作流开发指南 v1.0 四、流程中任务的分配 JBPM中任何一个task都必须指定一个任务的接收者,这个接收者可以是一个用户,也可以是一个用户组。如果指定给一个用户那么可以用这个用户的ID得到当前的task。如果是一个用户组那么这个组的任何一个用户都可以看到这个task,当这个组中的任何一个用户处理该任务后那么这个task对这个组中的其它用户就不再可见。 JBPM中任务的分配方式有两种:一种是为task指定一个assignment-handler,既一个实现了AssignmentHandler接口的类;另外一种是为task指定一个swimlane(泳道),swimlane可以在流程中定义好,一个流程中可以定义若干个swimlane,在定义一个swimlane时同样也是指定了个实现了AssignmentHandler接口的类,当我们的task指定了一个swimlane后,其效果同我们指定一个assignment-handler效果是一样的,只不过可以简化我们任务的分配工作。一个典型的实现了AssignmentHandler接口的类如下: package test.assignment; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.taskmgmt.def.AssignmentHandler; import org.jbpm.taskmgmt.exe.Assignable; public class ManagerAssignment implements AssignmentHandler{ public void assign(Assignable arg0, ExecutionContext arg1) throws Exception { String[] s=new String[5]; for (int i = 0; i < 5; i++) { s[i]="manager"+i; } //arg0.setActorId("manager0");//将任务分配给单个用户 arg0.setPooledActors(s);//将任务分配给一个用户组 第页 共107页 JBPM 工作流开发指南 v1.0 } } 4.1 assignment-handler方式的任务分配 在JBPM中可以在start-node中添加一个task,在一个task-node中添加若干个task。每一个task我们都必须为其指定一个assignment-handler或一个swimlane,二者只能选其一。 ... ... 上面的代码中start节点中我们添加了一个start task的task,同时为其指定了一个assignment,所对应的class为test.assignment.IssuePersonAssignment ,该类的代码如下: package test.assignment; import org.jbpm.graph.exe.ExecutionContext; 第页 共107页 JBPM 工作流开发指南 v1.0 import org.jbpm.taskmgmt.def.AssignmentHandler; import org.jbpm.taskmgmt.exe.Assignable; import test.common.Constants; public class IssuePersonAssignment implements AssignmentHandler { public void assign(Assignable arg0, ExecutionContext arg1) throws Exception { String issuePerson = arg1.getVariable(Constants.ISSUE_PERSON) .toString(); arg0.setActorId(issuePerson); } } 在该类中,我们从整个流程中的取出一个名为Constants.ISSUE_PERSON的流程变量,并将其赋给当前的task,这里采用的是Assignable的setActorId的方法。这样当用户登录时就可以用 JbpmContext context=JbpmConfiguration.getInstance().createJbpmContext(); List ls=context.getTaskList(session.getAttribute("username").toString()); 方法来取出对应的任务列表。 4.2 swimlane方式的任务分配 该种方式的任务分配实际上是对assignment-handler方式任务分配的简化。首先用户需要在流程中定义好若干个swimlane,接下来只需要在task中指定一个swimlane就可以完成任务的分配工作。 第页 共107页 JBPM 工作流开发指南 v1.0 ... ... ... 上面的代码中我们指定了一个叫manager的swimlane,然后我们在一个名为manager approve的task里将该task指定给该swimlane,这样就完成了任务的分配。从这里我们可以看到与assignment-handler方式分配最大不同之处是该种分配任务的方式简单、明了。我们只需要预先定义好若干个swimlane之后就可以在task里重复使用了。test.assignment.ManagerAssignment类的代码如下: package test.assignment; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.taskmgmt.def.AssignmentHandler; 第页 共107页 JBPM 工作流开发指南 v1.0 import org.jbpm.taskmgmt.exe.Assignable; public class ManagerAssignment implements AssignmentHandler{ public void assign(Assignable arg0, ExecutionContext arg1) throws Exception { String[] s=new String[5]; for (int i = 0; i < 5; i++) { s[i]="manager"+i; } arg0.setPooledActors(s);//将任务分配给一个用户组 } } 在这个类当中我们把任务分配给一个用户组(一个由用户ID组成的数组),这样该组中的每个用户登录后都可以采用以下方法看到任务列表: JbpmContext context=JbpmConfiguration.getInstance().createJbpmContext(); List list=context.getTaskMgmtSession().findPooledTaskInstances(session.getAttribute("username").toString()); 用这种方法,用户可以看到所有尚未处理的分配到该用户所在用户组中的task列表。一旦该组中有一个用户处理了该任务,那么这个任务对于其它用户就不再可见了。 五、JBPM持久化 JBPM采用Hibernate持久化到数据库,和其它一些工作流引擎一样,它的流程定义信息也要 第页 共107页 JBPM 工作流开发指南 v1.0 持久化到数据库中。由于JBPM采用Hibernate来和数据库打交道,理论上来说只要Hibernate支持的数据库JBPM都支持。在下面的篇幅里我们将介绍一下如何将JBPM的流程定义信息发布到数据库中,以及如何在Tomcat中进行流程的调用与处理。 5.1 特殊数据库支持 1) 在DB2上运行JBPM,我们首先需要运行类似下面的命令,以更改DB2的配置,然后才能开始我们JBPM的建表工作。 create bufferpool jbpm immediate size 1000 pagesize 32K create tablespace jbpm pagesize 32K managed by database using (file '/db2inst1/db2inst1/NODE0000/JBPM' 10M) AUTORESIZE YES bufferpool jbpm create temporary tablespace jbpmtemp pagesize 32K managed by database using (file '/db2inst1/db2inst1/NODE0000/JBPM_TMP' 10M) AUTORESIZE YES bufferpool jbpm 5.2 JBPM数据库的安装 我们以MSSQL2000为例和大家一起来看一下如何完成JBPM数据库的安装(其它数据库大家可以照葫芦画瓢完成)。 a) 在MSSQL里新建一数据库,名为jbpmtest(这里是我所用的数据库名,大家可以根据喜好,自己随意命名)。 b) 找到我们开始部分下载的jbpm-starters-kit-3.1.2文件夹,找到其中的jbpm文件夹。 c) 将MSSQL的JDBC驱动copy到jbpm文件夹的lib目录下(我这里采用的是sourceforge的jtds驱动) d) 在jbpm文件夹里在其中的src/resources下新建一文件夹名为mssql,将src/resources/hssqldb文件夹下的create.db.hibernate.properties和identity.db.xml文件copy到mssql目录下。 e)  修改create.db.hibernate.properties文件(这个文件里配置的是目标数据库的连接属性信息),修改内容如下: 第页 共107页 JBPM 工作流开发指南 v1.0 # these properties are used by the build script to create # a hypersonic database in the build/db directory that contains # the jbpm tables and a process deployed in there hibernate.dialect=org.hibernate.dialect.SQLServerDialect hibernate.connection.driver_class=net.sourceforge.jtds.jdbc.Driver hibernate.connection.url=jdbc:jtds:sqlserver://localhost:1433/jbpmtest hibernate.connection.username=sa hibernate.connection.password=gj hibernate.show_sql=true f) 在jbpm文件夹下,找到src\config.files\hibernate.cfg.xml.(此文件主要是系统运行时数据库连接属性配置),需要修改的内容如下: org.hibernate.dialect.SQLServerDialect net.sourceforge.jtds.jdbc.Driver jdbc:jtds:sqlserver://localhost:1433/jbpmtest     sa     gj g) 在Jbpm根目录下,打开build.deploy.xml文件,找到其中的create.db并做如下修改,create.db节点修改好的内容如下: 第页 共107页 JBPM 工作流开发指南 v1.0 h) 配置好ant工具,将ant/bin目录加上系统的path环境变量。 i) 在命令行模式下进入jbpm目录,输入ant create.db -buildfile build.deploy.xml ,构建JBPM所需要的table。这里大家可以观察屏幕输出,如果没有意外那就表明建表成功了! j) 打开MSSQL的企业管理器,我们可以看到一系列以“JBPM_”开头的table了,如下图: 第页 共107页 JBPM 工作流开发指南 v1.0 k) 至此,JBPM的建表工作完成。 5.2 JBPM流程发布 上一章节,我们已完成了对JBPM 的table的建立,接下来我们将继续探讨如何将我们的流程定义文件发布到JBPM数据库当中。 我们这里采用WEB应用的形式将流程定义文件发布到数据库当中。我们采用Tomcat 5.5.15做为我们的web server进行。 5.2.1 搭建JBPM的WEB应用 在JBPM的发布包中,已经为我们准备了一个JSF架构的WEB应用,我们可以在这个应用基础之上来搭建我们的发布工具。 a) 在命令行模式下,进入jbpm目录,运行ant命令编译打包JBPM工程。打包完成后进行 第页 共107页 JBPM 工作流开发指南 v1.0 jbpm/build目录,找到jbpm.war.dir文件夹,将其copy到tomcat 5.5.15的webapps目录下(由于我们前面在做JBPM建表工作的时候已经配置好JBPM的数据库连接信息,所以jbpm.war.dir的web应用里我们就不用再操心数据库的连接问题了)。 b) 将webapps下的jbpm.war.dir改名为jbpm(目的是为了简单) c) 打开jbpm目录,我们知道Hibernate运行时需要一些第三方jar包支持,但我们的jbpm目录里只有hibernate自己的jar包,如果这样运行tomcat5.5.15我们可以很明显地看到jbpm应用发布失败,如何解决这个问题呢,方法很简单。我们下载一个hibernate3的工程文件,将其中必须的第三方jar文件copy到我们的jbpm工程里就OK了,下图是我copy完成后的jbpm/WEB-INF/ lib目录下的jar包列表,供参考: 运行tomcat5.5.15 第页 共107页 JBPM 工作流开发指南 v1.0 打开IE,在地址栏里输入:http://localhost:8080/jbpm,这时如果我们操作正确的话,应该可以看到如下信息了: 这个JBPM的示例里已经有了一个流程示例,我们看到的便是这个应用的主窗口,大家可以用一下,体验一下JBPM工作流带来的乐趣。 5.2.2 发布第一个流程 JBPM里的那个流程示例这里不介绍了,有兴趣的可以自己研究一下。接下来我们来写一个servlet来发布我们的第一个流程。 启动Eclipse,编写发布的JSP页面deploy.jsp ,并将其copy到我们的jbpm应用的根目录下。 <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> 第页 共107页 JBPM 工作流开发指南 v1.0 Insert title here
 
请选择要发布的流程定义文件:
   
编写我们的流程发布Servlet,代码内容如下: 第页 共107页 JBPM 工作流开发指南 v1.0 package servlet; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import org.jbpm.graph.def.ProcessDefinition; import common.UploadFile; public class DeployWorkflowServlet extends HttpServlet{ protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException { UploadFile upload=new UploadFile(arg0); FileItem fileItem=upload.getFile("workflowfile"); InputStream fin=fileItem.getInputStream(); 第页 共107页 JBPM 工作流开发指南 v1.0 JbpmConfiguration config=JbpmConfiguration.getInstance(); JbpmContext context=config.createJbpmContext(); ProcessDefinition pd=ProcessDefinition.parseXmlInputStream(fin); context.deployProcessDefinition(pd);//发布上传过来的流程 context.close(); PrintWriter write=arg1.getWriter(); arg1.setContentType("text/html"); write.write("WorkFlow deploy success..........");//显示发布成功信息 write.close(); } } 编译将DeployWorkflowServlet.java,并将编译好的class其放入jbpm应用的classes目录下。配置到web.xml中,配置代码如下: deplyWorkflow servlet.DeployWorkflowServlet deplyWorkflow /deplyWorkflow.servlet 启动Tomcat5.5.15,访问http://localhost:8080/jbpm/deploy.jsp,我们将看到如下页面 第页 共107页 JBPM 工作流开发指南 v1.0 选择我们前面做过的流程定义文件,点击“提交”按钮 显示发布成功信息,打开我们的MSSQL的企业管理器,打开表 第页 共107页 JBPM 工作流开发指南 v1.0 “JBPM_PROCESSDEFINITION”,最后一条就是我们刚才发布的testsub流程,如下图 OK,我们的第一个流程发布成功。 六、日历(Scheduler) JBPM的Scheduler可以实现在JBPM流程中定时触发某一动作。在流程中JPBM提供了timer节点供我们使用,通过这个节点我们可以实现节点动作的定时触发。定时器timer可以被用于decision、fork、join、node、process-state、state、super-state、task-node,可以设置开始时间duedate和频率repeat,定时器动作可以是所支持的任何动作元素,如action、script、create-timer、cancel-timer,来执行我们设置的商务动作。我们可以把Scheduler理解成是一个后台线程在不停的监听着timer(jbpm_timer表),如果有需要触发的timer生成了,就按照timer的属性定时或者循环触发它。 第页 共107页 JBPM 工作流开发指南 v1.0 jbpm提供了2种调用scheduler的方法: 一种是用在web应用的,采用org.jbpm.scheduler.impl.SchedulerServlet,具体的使用方法在JBPM提供的javadoc里有很好的示例,我们只需在web.xml中加载它就行了; 另一种是针对的C/S程序,jbpm提供了一个很好的示例org.jbpm.scheduler.impl.SchedulerMain,我们可以参照它编写我们自己的Scheduler。 6.1 Scheduler在C/S程序上的应用 下面我就编写一个cs程序来实现Scheduler,并调用一个最简单的timer。这个timer从第5秒开始每隔3秒执行script中的内容。 transition> transition> end-state> package test; import org.jbpm.*; import org.jbpm.graph.def.ProcessDefinition; 第页 共107页 JBPM 工作流开发指南 v1.0 import org.jbpm.graph.exe.*; import org.jbpm.scheduler.impl.Scheduler; public class Test { static JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance(); static ProcessDefinition processDefinition = null; static ProcessInstance processInstance = null; static Scheduler scheduler = null; public static void initSchedular() {//设置Schedular的属性 scheduler = new Scheduler(); int interval = 5000; scheduler.setInterval(interval); int historyMaxSize = 0; scheduler.setHistoryMaxSize(historyMaxSize); scheduler.start(); } public static void destroy() {//这个例子没用到 scheduler.stop(); } static class MySchedularThread extends Thread{//实际业务处理线程 public void run(){ JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { long processInstanceId =1; processInstance = jbpmContext.loadProcessInstance(processInstanceId); Token token = processInstance.getRootToken(); System.out.println(token.getNode()); //一定要运行到有timer生成,触发 第页 共107页 JBPM 工作流开发指南 v1.0 token.signal(); System.out.println(token.getNode()); jbpmContext.save(processInstance); //如果这里程序到这里退出的话可以看到jbpm_timer表里有一条数据 Thread.sleep(30*1000);//为模拟效果,此线程停止30秒 //节点跳过,timer结束,jbpm_timer表该数据清空 token.signal(); System.out.println(token.getNode()); jbpmContext.save(processInstance); }catch(Exception e){ e.printStackTrace(); }finally { jbpmContext.close(); } } } public static void main(String[] args) { initSchedular (); MySchedularThread mst=new MySchedularThread(); mst.start(); } } 运行结果: StartState(start) State(a) node enter node enter node enter node enter node enter EndState(end) 第页 共107页 JBPM 工作流开发指南 v1.0 从上面的例子的运行结果当中我们可以看到,当流程开始后进入名为“a”的state节点,进入名为“a”的state节点五秒后开始启动名为reminder的timer,该timer每隔3秒运行一次,直到token离开当前节点。 6.2 Scheduler 在Web上的应用 Scheduler在Web上的应用相对来说比较简单,我们只需把org.jbpm.scheduler.impl.SchedulerServlet配置到我们的web.xml中,然后在我们的流程中配置好timer就可以完成我们的流程的调度。 …… SchedulerServlet org.jbpm.scheduler.impl.SchedulerServlet interval 5000 historyMaxSize 50 1 第页 共107页 JBPM 工作流开发指南 v1.0 SchedulerServlet /jbpmscheduler …… 流程配置示例: 第页 共107页 JBPM 工作流开发指南 v1.0 启动流程代码: JbpmConfiguration config=JbpmConfiguration.getInstance(); JbpmContext context=config.getCurrentJbpmContext(); ProcessDefinition pd=context.getGraphSession().findLatestProcessDefinition(“test”); ProcessInstance pi=pd.createProcessInstance(); pi.getContextInstance().setVariable("reminderTestDueDate", arg1.getParameter("reminderTestDueDate")); pi.getContextInstance().setVariable("taskuser", arg1.getParameter("taskuser")); pi.signal(); 在上面的示例流程中,一旦我们在代码中启动流程,流程就开始进入state1节点,进入该节点后5秒开始reminder这个timer ,reminder会每隔6秒钟触发一次,在进入state1后的30秒后触发endReminder这个timer,该timer只会触发一次,触发的时候会启动其内部的script和cancel-timer,cancel-timer可以用来取消其name属性所指定的timer。我们这里指定的是前面的reminder,所以当endReminder启动后会结束掉前面的reminder这个timer。同时当endReminder执行完成后,流程会按endReminder的transition属性所指定的transition离开当前的state节点进入tr2所指定的路线进入node1节点,同时执行node1节点的script动作,运行结果如下: 第页 共107页 JBPM 工作流开发指南 v1.0 reminder start ***Sun Jun 24 14:15:16 CST 2007 reminder start ***Sun Jun 24 14:15:22 CST 2007 reminder start ***Sun Jun 24 14:15:28 CST 2007 reminder start ***Sun Jun 24 14:15:34 CST 2007 reminder start ***Sun Jun 24 14:15:40 CST 2007 endReminder start ***Sun Jun 24 14:15:45 CST 2007 enter node1 ***Sun Jun 24 14:15:45 CST 2007 6.3 Scheduler时间的分类 关于duedate的格式,可以分为两种。一种是我们上面所用到的可以把它叫做绝对时间,比如5 seconds,5 days,5 months…(5秒,5天,5个月…),可以使用的有second,seconds,minute,minutes,hour,hours,day,days,week,weeks,month,months,year,years;第二种叫业务时间格式,就是在绝对时间里加上business,也就是说加了business就叫业务时间。具体的业务时间怎么去界定呢?我们可以打开jbpm*.jar里的文件org/jbpm/calendar/jbpm.business.calendar.properties指定了什么是业务时间 hour.format=HH:mm #weekday ::= [ [& ]*] #daypart ::= - #start-hour and to-hour must be in the hour.format #dayparts have to be ordered weekday.monday= 9:00-12:00 & 12:30-17:00 weekday.thuesday= 9:00-12:00 & 12:30-17:00 weekday.wednesday= 9:00-12:00 & 12:30-17:00 weekday.thursday= 9:00-12:00 & 12:30-17:00 weekday.friday= 9:00-12:00 & 12:30-17:00 weekday.saturday= weekday.sunday= day.format=dd/MM/yyyy 第页 共107页 JBPM 工作流开发指南 v1.0 # holiday syntax: # holiday period syntax: - # below are the belgian official holidays holiday.1= 01/01/2005 # nieuwjaar holiday.2= 27/3/2005 # pasen holiday.3= 28/3/2005 # paasmaandag holiday.4= 1/5/2005 # feest van de arbeid holiday.5= 5/5/2005 # hemelvaart holiday.6= 15/5/2005 # pinksteren holiday.7= 16/5/2005 # pinkstermaandag holiday.8= 21/7/2005 # my birthday holiday.9= 15/8/2005 # moederkesdag holiday.10= 1/11/2005 # allerheiligen holiday.11= 11/11/2005 # wapenstilstand holiday.12= 25/12/2005 # kerstmis business.day.expressed.in.hours= 8 business.week.expressed.in.hours= 40 business.month.expressed.in.business.days= 21 business.year.expressed.in.business.days= 220 上面定义通俗的理解就是它归定了从星期一到星期五的9:00-12:00 & 12:30-17:00   这段时间为上班时间也就是业务时间,星期六和星期日没有定义也就是放假的时间,再往下就是定义了12个节假日,最后是一些工作时长的统计如一天8小时,一个星期40小时等。 下面我们定义一个定时器: 第页 共107页 JBPM 工作流开发指南 v1.0 这里的定时器(timer)的名字是reminder,它的duedate定义的是3 business day,repeat定义的是2 business day。也就是说从定时器启动开始在3个业务日的时间后每隔两个业务日执行一次action的方法,直到timer结束。现在就可以来区别业务时间和绝对时间了,假设我是在星期五的早上10点启动了这个timer,那它第一次执行action的方法是在什么时候呢?因为这里的定义是有加上business的所以要结束工作日历的定义来算了。工作日历中定义了星期六和星期天是不上班的(没有定义),所以在计算时间时就跳过,要到星期三到早上10点(这时只是执行完duedate的时间也就是第一次触发action的时间),到星期五早上10点第二次触发action,到下个星期二早上第二次触发action(因为星期六和星期日没定义不是业务时间),以此类推。如果在timer定义时没有加上business的话,就以绝对时间进行计算,也就是在星期一的10点第一次触发action,到星期三10点第二次触发action,以此类推。 七、异步执行 从前面的JPDL流程定义语言里我们知道了很多节点都有async属性,当该属性设置为true时表示是异步执行,否则表示同步执行,其默认值为false。通常在同步状态下,在JBPM流程中节点总是在令牌(Token)进入之后被执行,因此,节点在客户端线程中被执行。同步执行要把所有的业务代码执行完成,和流程离开当前节点后进入下面的节点后代码全部执行完成后才能完成整个一个操作过程,所以如果流程中的逻辑比较复杂的话,给我们的感觉就是时间较久,如果是异步则恰恰相反。 在jBPM中,异步执行通过使用一个异步通知系统来实现。当流程执行到达需要异步执行的节点时,jBPM将挂起执行,产生一个命令消息并发送该命令消息到命令执行器,命令执行器是一个单独的组件,在收到的消息之上它将在流程挂起的地方恢复流程执行。事务因此也将由一个被分裂为两个独立的事务,每个事务对应于一部分。这样对于一些在流程中在节点里或 第页 共107页 JBPM 工作流开发指南 v1.0 Action里需要花费较长时间执行的逻辑代码可以把节点或Action的async的属性打开,设置成异步执行。 八、JBPM流程建模与应用 8.1 JBPM的建模工具 我们前面所做的几个流程,全部是我们通过手写XML的方式实现的,个人觉得,这种方式对于我们熟练JPDL语法,熟悉JPBM的建模方式是很有帮助的。如果我们的对JPDL已以熟悉了要做一些复杂流程建模时手写的工作效率是很难尽如人意的。这个时候我们就迫切需要一个可视化的建模工具来帮助我们提高建模的工作效率,很幸运,JPBM为我们提供了一个基于Eclipse的可视化建模工具。 8.1.1 建模工具的安装 打开我们前面下载的jbpm-starters-kit-3.1.2工程包文件,打开其中的jbpm-designer目录,我们有两种方式把这个插件安装到Eclipse当中,一种是采用link的方式,在jbpm-designer\eclipse\links目录下已经为我们准备好了link文件,还有一种就是把jbpm-designer\jbpm-gpd-feature\eclipse目录下的两个文件夹里的内容copy到Eclipse安装目录下对应的目录里。这里我们采用的是后一种方式。 在命令行模式下,进入Eclipse所在目录,输入eclipse –clean启动Eclipse。 启动完成后,打开新建窗口,我们可以看到Jboss jbpm节点,如下图,点开该节点,我们利用它提供的向导新建一个JBPM Project名为test-jbpm 第页 共107页 JBPM 工作流开发指南 v1.0 建好的JPBM工程目录结构图 第页 共107页 JBPM 工作流开发指南 v1.0 打开工程的processes目录,我们可以看到,JBPM设计器已经帮我们建好了一个示例流程的模型,双击打开processdefinition.xml便可以看到该流程模型的图形化表示,如下图: 我们的JBPM建模工具安装成功。 第页 共107页 JBPM 工作流开发指南 v1.0 8.2 公司报销流程示例 该示例演示的是一个公司报销流程。普通员工可以填写报销单,然后提交主管审批;主管审批可以有三种可能:一是主管可以驳回请求,那么报销人需要重填报销单,或者取消报销操作;二是主管不同意请求,请求直接结束;三是主管同意请求,那又存在两种情况,一是如果报销总费用大于1000的话那么会自动转到老板那里,如果小于1000就直接进入财务处理子流程,老板审批的话有两种可能,一是同意进入财务处理子流程,二是不同意请求直接结束。 财务处理流程里面只有一个Node节点,自动执行一个Action,并没有做什么特殊处理。这里加上这么一个财务处理子流程目的是介绍一下子流程用法。 8.2.1 流程建模 公司报销流程模型图如下: 第页 共107页 JBPM 工作流开发指南 v1.0 公司报销流程模型XML定义代码如下: 第页 共107页 JBPM 工作流开发指南 v1.0 第页 共107页 JBPM 工作流开发指南 v1.0 财务处理子流程的流程图如下: 对应的XML代码如下: 第页 共107页 JBPM 工作流开发指南 v1.0 注:因为子流程和主流程是一样的,所以我们这里没有必要把子流程做的那么复杂,财务子流程没有任务节点,只有一个自动节点,在这个自动节点里只是添加了一个Action用来输出从主流程中得到的流程变量。这个Action的代码如下: /* * CreatePerson:gaojie CreateDate:May 10, 2007 10:41:55 AM */ package demo.workflow.action; import org.jbpm.graph.def.ActionHandler; import org.jbpm.graph.exe.ExecutionContext; public class CaiwuProcessAction implements ActionHandler{ public void execute(ExecutionContext arg0) throws Exception { Object baoxiaoId=arg0.getContextInstance().getVariable("baoxiaoId"); System.out.println("*************财务子流程中报销ID(baoxiaoId):"+baoxiaoId); } 第页 共107页 JBPM 工作流开发指南 v1.0 } 主流程中普通员工用的任务分配类UserAssignment代码: /* * CreatePerson:gaojie CreateDate:May 10, 2007 10:13:05 AM */ package demo.workflow.assignment; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.taskmgmt.def.AssignmentHandler; import org.jbpm.taskmgmt.exe.Assignable; import demo.common.Constants; public class UserAssignment implements AssignmentHandler{ public void assign(Assignable arg0, ExecutionContext arg1) throws Exception { String issueUser=(String)arg1.getContextInstance().getVariable(Constants.ISSUE_USER); arg0.setActorId(issueUser); } } 主流程中主管任务分配类ManagerAssignment代码如下: /* * CreatePerson:gaojie CreateDate:May 10, 2007 10:34:11 AM */ package demo.workflow.assignment; import java.util.List; import org.hibernate.Session; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.taskmgmt.def.AssignmentHandler; 第页 共107页 JBPM 工作流开发指南 v1.0 import org.jbpm.taskmgmt.exe.Assignable; import demo.domain.TbUser; public class ManagerAssignment implements AssignmentHandler{ public void assign(Assignable arg0, ExecutionContext arg1) throws Exception { Session session=arg1.getJbpmContext().getSession(); String hql="from TbUser u where u.userType=1"; List userList=session.createQuery(hql).list(); String[] user=new String[userList.size()]; for (int i = 0; i < userList.size(); i++) { TbUser u=(TbUser)userList.get(i); user[i]=u.getUserName(); } arg0.setPooledActors(user); } } 主流程中老板任务分配类BossAssignment代码如下: /* * CreatePerson:gaojie CreateDate:May 10, 2007 10:34:11 AM */ package demo.workflow.assignment; import java.util.List; import org.hibernate.Session; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.taskmgmt.def.AssignmentHandler; import org.jbpm.taskmgmt.exe.Assignable; import demo.domain.TbUser; public class BossAssignment implements AssignmentHandler{ public void assign(Assignable arg0, ExecutionContext arg1) throws Exception { 第页 共107页 JBPM 工作流开发指南 v1.0 Session session=arg1.getJbpmContext().getSession(); String hql="from TbUser u where u.userType=2"; List userList=session.createQuery(hql).list(); String[] user=new String[userList.size()]; for (int i = 0; i < userList.size(); i++) { TbUser u=(TbUser)userList.get(i); user[i]=u.getUserName(); } arg0.setPooledActors(user); } } 主流程中常量定义类Constants代码如下: /* * CreatePerson:gaojie CreateDate:May 10, 2007 11:06:32 AM */ package demo.common; public class Constants { public static final String MANAGER_AGREE="主管同意"; public static final String BOSS_AGREE="老板同意"; public static final String DISAGREE="不同意"; public static final String ISSUE_USER="issueUser"; } 主流程中在“判断走向”这个节点中的决定流程走向的Decision类代码如下: /* * CreatePerson:gaojie CreateDate:May 10, 2007 10:45:29 AM */ package demo.workflow.action; 第页 共107页 JBPM 工作流开发指南 v1.0 import java.util.Iterator; import java.util.List; import org.hibernate.Session; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.node.DecisionHandler; import demo.common.Constants; import demo.domain.TbApprove; import demo.domain.TbBaoxiaoItem; /** * 该类是用来处理主管审批完成后,流程该走向哪里。 * */ public class DecisionProcess implements DecisionHandler{ public String decide(ExecutionContext arg0) throws Exception { //默认直接进入最后处理结果节点 String go="to result"; Object baoxiaoId=arg0.getContextInstance().getVariable("baoxiaoId"); Session session=arg0.getJbpmContext().getSessionFactory().openSession(); String hql="from TbApprove a where a.tbBaoxiao="+baoxiaoId+" order by a.approveId desc"; TbApprove approve=(TbApprove)session.createQuery(hql).iterate().next(); String result=approve.getApproveResult(); //如果主管审批同意的话,进一步处理 if(result.equals(Constants.MANAGER_AGREE)){ hql="from TbBaoxiaoItem b where b.tbBaoxiao="+baoxiaoId+""; List ls=session.createQuery(hql).list(); int amount=0; for (Iterator iter = ls.iterator(); iter.hasNext();) { 第页 共107页 JBPM 工作流开发指南 v1.0 TbBaoxiaoItem b = (TbBaoxiaoItem) iter.next(); amount+=Integer.parseInt(b.getItemMoney()); } //当报销总金额大于1000时提交到老板去审批 if(amount>1000){ go="to boss"; }else{ //否则直接进入财务处理子流程 go="to caiwu"; } } session.close(); //返回最终结果 return go; } } 8.2.2 流程数据库搭建 我这里采用的是mssql2000数据库。 解压Jbpm-3.x,我这里用的是Jbpm-3.1.2,我放D盘根目录下,解压后的文件结构如下: 第页 共107页 JBPM 工作流开发指南 v1.0 在src/resources目录下添加一个文件夹,比如叫mssql,然后从边上的hql数据库里拷贝create.db.hibernate.properties和identity.db.xml文件到mssql文件夹,修改其中的create.db.hibernate.properties中的数据库连接信息,我这边修改如下: # these properties are used by the build script to create # a hypersonic database in the build/db directory that contains # the jbpm tables and a process deployed in there hibernate.dialect=org.hibernate.dialect.SQLServerDialect hibernate.connection.driver_class=net.sourceforge.jtds.jdbc.Driver hibernate.connection.url=jdbc:jtds:sqlserver://localhost:1433/jbpm_test hibernate.connection.username=sa hibernate.connection.password= hibernate.show_sql=true 这里采用的MSSQL的驱动为apache的jtds,所以我们要把jtds-1.2.jar copy到jbpm-3.1.2目录里的lib目录下。 修改src/config.files/hibernate.cfg.xml文件,大家知道这个文件是Hibernate的数据库配置文件,对照create.db.hibernate.properties文件中的数据库连接信息,我们对hibernate.cfg.xml的中数据库连接部分做了如下修改: …… 第页 共107页 JBPM 工作流开发指南 v1.0 org.hibernate.dialect.SQLServerDialect net.sourceforge.jtds.jdbc.Driver jdbc:jtds:sqlserver://localhost:1433/jbpm_test sa …… jbpm_test是我们在MSSQL里建的一个数据库 接下来我们需要修改根目录下的build.deploy.xml文件,这里主要是修改名为create.db的target,修改代码如下: …… 第页 共107页 JBPM 工作流开发指南 v1.0 …… 完成以上工作后,我们就可以利用ant来帮助我们构建JBPM的数据库表了。 打开windows的dos窗口,切换到jbpm-3.1.2目录,运行ant create.db –buildfile build.deploy.xml命令即可完成JBPM表的构建工作。构建好的表截图如下: 8.2.3 构建业务表 在这个例子当中,用户采用外部的用户,在前面介绍的三个Assignment中已经体现出来。这里我们涉及到的业务表主要有:用户表(流程中任务的分配)、报销表、报销项目表(与报销表之间有一种主从关系)、审核意见表(对报销的审批历史记录),MSSQL2000的建库脚本如下: 第页 共107页 JBPM 工作流开发指南 v1.0 --用户表 create table tb_user( user_id int primary key identity(1,1),--用户ID user_name varchar(30),--用户名 user_password varchar(30),--密码 user_type int --用户类型(0为普通用户,1为主管,2为老板,3为财务人员) ) go --报销表 create table tb_baoxiao( baoxiao_id int primary key identity(1,1), baoxiao_title varchar(30),--报销主题 baoxiao_memo varchar(30),--备注 user_id int,--报销人 baoxiao_date datetime, --报销时间 baoxiao_flag bit --报销状态(0为未处理,1为已处理) ) go --报销项目表 create table tb_baoxiao_item( item_id int primary key identity(1,1), item_name varchar(30),--项目名称 item_money varchar(100),--项目金额 item_memo varchar(200),--项目备注 baoxiao_id int --报销表ID ) go --审核意见表 第页 共107页 JBPM 工作流开发指南 v1.0 create table tb_approve( approve_id int primary key identity(1,1), user_id int,--审核人ID baoxiao_id int,--报销表ID approve_result varchar(30),--审核结果 approve_memo varchar(30),--审核意见 approve_date datetime --审核日期 ) go --为报销表添加外键 alter table tb_baoxiao add CONSTRAINT baoxiao_foreign_key foreign key (user_id) references tb_user(user_id) go --为报销项目表添加外键 alter table tb_baoxiao_item add CONSTRAINT baoxiao_item_foreign_key foreign key (baoxiao_id) references tb_baoxiao(baoxiao_id) go --为审核意见表添加外键 alter table tb_approve add CONSTRAINT approve_user_foreign_key foreign key (user_id) references tb_user(user_id) go alter table tb_approve add CONSTRAINT approve_baoxiao_foreign_key foreign key (baoxiao_id) references tb_baoxiao(baoxiao_id) go ---对用户表进行初始化 Insert into tb_user(user_name,user_password,user_type) values(‘test’,’test’,0) Go Insert into tb_user(user_name,user_password,user_type) values(‘manager’,’manager’,1) 第页 共107页 JBPM 工作流开发指南 v1.0 Go Insert into tb_user(user_name,user_password,user_type) values(‘boss’,’boss’,2) go 打开MSSQL2000的查询分析器,切换到jbpm_test数据库,执行上面这段SQL,完成我们的业务表的构建工作。 8.2.4 搭建工程 搭建工程我们有一种很简单的办法。我们知道jbpm-3.1.2里会自带一个sample工程,我们可以把这个工程copy一份,在这个工程的基础之上来搭建我们的应用。 为了能快速的构建表现层这块,我们采用了目前国内领先的J2EE表现层产品Dorado5来实现(详情请见Dorado的官方网站:http://www.bstek.com)。 Jbpm的Sample工程与Dorado结合后的详细代码请参考附件,也可以与本人联系索取,,拼好后的工程的目录结构如下: 第页 共107页 JBPM 工作流开发指南 v1.0 8.2.5 报销流程的发布 在第五章节点我们已经介绍了JBPM流程的发布方式,这里我们采用流程引擎IDE工具提供的发布方式。 我们当前的工程是在MyEclipse5.5的环境下编辑调试的,首先我们要在MyEclipse5.5里配置好一个应用服务器,我们这里采用的是Tomcat5.5。之后将我们前面做的工程导入到MyEclipse里,并添加Web capability,打开Server view,将我们的JBPM工程发布到Tomcat5.5中并启动Tomcat5.5。打开我们的财务处理子流程(这里有一点需要注意,在JBPM中如果涉及到子流程,一定要先发布子流程再发布主流程,不然可能会造成找不到子流程的错误),切换到Deployment窗口,设置好server name 、server port 和server deployer三个属性的值,可以先点击test connection按钮看一下连接是否正确,如果没有问题就可以点击Deploy process archive按钮来发布我们的流程,如下图: 主流程可以参照子流程的方式发布。 发布完成后我们应该可以在jbpm_test库里Jbpm_processdefinition表里看到我们发布成功的两个流程: 第页 共107页 JBPM 工作流开发指南 v1.0 8.2.6 应用程序搭建 1) 用户登录 用户登录的页面比较简单,利用Dorado工具就更加简单。建好的Dorado ViewModel如下: 第页 共107页 JBPM 工作流开发指南 v1.0 提交处理代码如下: package dorado.login; import java.util.List; import org.hibernate.Session; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import com.bstek.dorado.common.DoradoContext; import com.bstek.dorado.data.ParameterSet; import com.bstek.dorado.view.DefaultViewModel; import demo.domain.TbUser; 第页 共107页 JBPM 工作流开发指南 v1.0 /** * LoginView */ public class LoginView extends DefaultViewModel { public void login(ParameterSet parameters, ParameterSet outParameters) throws Exception { String username=parameters.getString("username"); String userpwd=parameters.getString("userpwd"); String hql="from TbUser u where u.userName='"+username+"' and u.userPassword='"+userpwd+"'"; JbpmConfiguration config=JbpmConfiguration.getInstance(); JbpmContext context=config.getCurrentJbpmContext(); Session session=context.getSessionFactory().openSession(); List ls=session.createQuery(hql).list(); session.close(); if(ls.size()>0){ TbUser user=(TbUser)ls.get(0); DoradoContext.getContext().setAttribute(DoradoContext.SESSION, "username", username); DoradoContext.getContext().setAttribute(DoradoContext.SESSION, "userId", user.getUserId()); DoradoContext.getContext().setAttribute(DoradoContext.SESSION, "user_type", user.getUserType()); }else{ throw new IllegalArgumentException("用户名密码有误。"); } 第页 共107页 JBPM 工作流开发指南 v1.0 } } 运行效果如下: 登录成功后既出现操作主界面。 第页 共107页 JBPM 工作流开发指南 v1.0 主界面同样采用Dorado实现,左边和上边是分级导航和导航菜单。工作区是当前用户收到的消息,消息的传递我们采用Jbpm提供的消息功能实现。 2) 报销流程的发起与审批(申请报销) 申请报销也既是发起流程,在这里主要涉及到两张表,报销表和报销项目表。这两张表的关系是主从关系,Dorado的ViewModel如下: 第页 共107页 JBPM 工作流开发指南 v1.0 运行效果如下: 第页 共107页 JBPM 工作流开发指南 v1.0 当点击保存报销内容时,处理代码如下: package dorado.apply; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.Transaction; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.taskmgmt.exe.TaskInstance; import com.bstek.dorado.common.DoradoContext; import com.bstek.dorado.data.Dataset; import com.bstek.dorado.data.ParameterSet; import com.bstek.dorado.data.RecordIterator; import com.bstek.dorado.view.DefaultViewModel; import demo.common.Constants; import demo.domain.TbBaoxiao; import demo.domain.TbBaoxiaoItem; import demo.domain.TbUser; /** * ApplyView */ public class ApplyView extends DefaultViewModel { 第页 共107页 JBPM 工作流开发指南 v1.0 public void saveAll(ParameterSet parameters, ParameterSet outParameters) throws Exception { Dataset d=this.getDataset("datasetBaoxiao"); TbBaoxiao bx=(TbBaoxiao)d.toSingleDO(); DoradoContext context=DoradoContext.getContext(); TbUser user=new TbUser(); user.setUserId((Integer)context.getAttribute(context.SESSION,"userId")); bx.setTbUser(user); bx.setBaoxiaoFlag(new Byte("0")); Dataset dd=this.getDataset("datasetBaoxiaoItem"); RecordIterator iter=dd.recordIterator(); Set s=new HashSet(); while(iter.hasNext()){ TbBaoxiaoItem item=(TbBaoxiaoItem)dd.toSingleDO(iter.nextRecord()); item.setTbBaoxiao(bx); s.add(item); } bx.setTbBaoxiaoItems(s); JbpmConfiguration config=JbpmConfiguration.getInstance(); JbpmContext jbpmContext=config.createJbpmContext(); Session session=jbpmContext.getSessionFactory().openSession(); Transaction trans=session.beginTransaction(); try { session.save(bx); ProcessDefinition pd=jbpmContext.getGraphSession().findLatestProcessDefinition("baoxiao"); ProcessInstance pi=pd.createProcessInstance(); pi.getContextInstance().setVariable(Constants.ISSUE_USER, context.getAttribute(context.SESSION,"username")); 第页 共107页 JBPM 工作流开发指南 v1.0 TaskInstance ti=pi.getTaskMgmtInstance().createStartTaskInstance(); ti.setVariable("baoxiaoId", bx.getBaoxiaoId()); ti.end(); trans.commit(); } catch (Exception e) { trans.rollback(); e.printStackTrace(); throw e; }finally{ session.close(); jbpmContext.close(); } super.doUpdateData(parameters, outParameters); } } 当我们点击“保存报销内容”按钮时就触发上面的处理代码,进行业务数据的保存和流程的开始处理。这样就manager1用户就可以在待处理任务里看到当前用户提交的审批任务。 第页 共107页 JBPM 工作流开发指南 v1.0 这里有个地方需要说明一下,对于JBPM来说,我们可以把任务分给一个用户或者一个用户组(多个用户),分给一个用户就是我们这里把它叫他“个人任务”,分给一组用户的话因为每个用户登录后都可以看到该任务,都可以处理该任务,所以我们把它叫“共享任务”。在这里我们规定,对于个人任务当前用户可以直接去处理,对于共享任务当前用户如果要处理该任务要首先把该任务拉到自己的任务列表里,然后才可以对其进行处理。把任务拉到自己任务列表的方法代码如下: package dorado.tasklist; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import org.jbpm.taskmgmt.exe.TaskInstance; import com.bstek.dorado.common.DoradoContext; import com.bstek.dorado.data.ParameterSet; import com.bstek.dorado.view.DefaultViewModel; 第页 共107页 JBPM 工作流开发指南 v1.0 /** * TaskListView */ public class TaskListView extends DefaultViewModel { public void changeTask(ParameterSet parameters, ParameterSet outParameters) throws Exception { JbpmConfiguration config=JbpmConfiguration.getInstance(); JbpmContext jbpmContext=config.createJbpmContext(); DoradoContext context=DoradoContext.getContext(); TaskInstance ti=jbpmContext.getTaskInstance(parameters.getLong("taskId")); String username=context.getAttribute(context.SESSION,"username").toString(); ti.setActorId(username); jbpmContext.save(ti); jbpmContext.close(); } } 对于当前任务,我们还可以利用JBPM提供的流程进度图来查看任务当前在流程中所处的位置,如下图: 第页 共107页 JBPM 工作流开发指南 v1.0 把共享任务拉到自己的任务列表后我们就可以针对不同的任务,设定不同的URL来做具体的业务页面对其做相应的处理。 如主管审核的操作页面效果如下: 第页 共107页 JBPM 工作流开发指南 v1.0 点击“提交审核意见”按钮对应的处理代码如下: package dorado.approve; import java.util.Date; import org.hibernate.Session; import org.hibernate.Transaction; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import org.jbpm.taskmgmt.exe.TaskInstance; import com.bstek.dorado.common.DoradoContext; import com.bstek.dorado.data.Dataset; import com.bstek.dorado.data.ParameterSet; import com.bstek.dorado.view.DefaultViewModel; 第页 共107页 JBPM 工作流开发指南 v1.0 import com.bstek.dorado.view.control.dropdown.ListDropDown; import demo.domain.TbApprove; import demo.domain.TbUser; /** * ApproveView */ public class ApproveView extends DefaultViewModel { protected void initControls() throws Exception { super.initControls(); ListDropDown dd=(ListDropDown)this.getControl("dropdownResult"); Integer user_type=(Integer)DoradoContext.getContext().getAttribute(DoradoContext.SESSION, "user_type"); if(user_type.intValue()==2){ dd.addItem("老板同意"); }else{ dd.addItem("主管同意"); } dd.addItem("不同意"); dd.addItem("重填申请"); } public void saveTask(ParameterSet arg0, ParameterSet arg1) throws Exception { Dataset d = this.getDataset("datasetAdvice"); TbApprove approve = (TbApprove) d.toSingleDO(); DoradoContext context = DoradoContext.getContext(); TbUser user = new TbUser(); user.setUserId((Integer) context .getAttribute(context.SESSION, "userId")); approve.setApproveDate(new Date()); 第页 共107页 JBPM 工作流开发指南 v1.0 approve.setTbUser(user); JbpmConfiguration config = JbpmConfiguration.getInstance(); JbpmContext jbpmContext = config.createJbpmContext(); Session session = jbpmContext.getSessionFactory().openSession(); String taskId = arg0.getString("taskId"); Transaction trans=session.beginTransaction(); try { session.save(approve); trans.commit(); } catch (Exception e) { trans.rollback(); e.printStackTrace(); throw e; } finally { session.close(); } TaskInstance ti = jbpmContext.getTaskInstance(Long.valueOf(taskId)); String result = approve.getApproveResult(); if (result.equals("重填申请")) { ti.end("need mod"); } else { if(result.equals("老板同意")){ ti.end("to caiwu"); }else{ ti.end("to result"); } } jbpmContext.close(); super.doUpdateData(arg0, arg1); 第页 共107页 JBPM 工作流开发指南 v1.0 } } 这时当我们再次查看待处理任务列表时我们会发现刚才处理的任务已从里面消失。其它的业务处理代码我们这里就不再介绍了,因为前面介绍的大体上是差不多的,详细可以去参考用Dorado编写的Jbpm-demo这个工程。 九、写在最后 到这里JBPM的教程算是告一段落,由于本人对JBPM的了解也只是皮毛,所以文中错误地方在所难免,请见谅。欢迎大家多提宝贵意见,我的邮件地址是 jacky.gao@bstek.com。 JBPM目前的最新的版本是3.2.1,在这个版本里增加了很多新的功能,比如对EJB的支持等,同时也修正了许多BUG,详细情形请参考JBOSS网站:http://www.jboss.org 文档信息 文档中文名称 JBPM工作流开发指南 文档英文名称 JBPMWorkflowDevelopmentGuide 文档内容简介 基于JBPM的工作流开发指南,包括JPDL讲解、流程中任务的分配、JBPM持久化、日历(Scheduler)、异步执行、JBPM流程建模与应用等。 文档分享范围 公开文档 日期 作者 版本 变更说明 2007年7月6日 Jacky.gao@bstek.com V1.0 创建 第页 共107页 JBPM 工作流开发指南 v1.0 第页 共107页

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享文档获得金币 ]
10 人已下载

下载文档

相关文档