dust的专栏

记录成长的地方

原创 一个Jbpm员工请假流程的实例收藏

新一篇: jbpm的任务管理实现  | 旧一篇:  无所不能的ant

一个Jbpm员工请假流程的实例

 

 

作者:吴大愚

Email:dywu_xa@sina.com

2006-10-26

 

 

适用于jbpm3.1版本

1.   概述

此实例包括的是一个员工请假审批的流程实例,和流程相关的代码以及相应的测试代码。此流程在Eclipse3.1.2 ,JBoss-IDE 1.6环境下测试通过。

说明,这篇文章说使用的流程实例是,学习《一个JBPM工作流管理示例》文章中的流程而来。原文中的流程实例不是jbpm3.1版本,不能适用于jbpm3.1。本人将其改写,并加入自己的设计和实现。原文地址为http://blogger.org.cn/blog/more.asp?name=lhwork&id=16137。可以对照学习。

2.   流程说明

假设应用背景如下:

在某一公司中,部门员工要休假的话需要部门主管的批准。如果休假天数大于10天的话,在部门主管的同意后,还必须老板批准。如果是部门主管要休假只要老板批准即可。在休假被批准之前,申请人可以撤销休假申请。

每次休假申请结束之后,不管通过未通过或是否取消,都必须记录下来。主管在批复申请之后,系统要将批复结果Email给申请人。对于大于10天的申请,如果部门主管已批准同意而上级主管还未批准,这时申请人撤销申请后,系统应发Email通知部门主管申请已撤销。 

3.   流程定义

3.1. 原文件

<?xml version="1.0" encoding="UTF-8"?>

<process-definition

  xmlns="urn:jbpm.org:jpdl-3.1"  name="MyRequest">

   <start-state name="SS_Request">

 

      <transition name="" to="TN_WriteRequest"></transition>

   </start-state>

   <task-node name="TN_WriteRequest">

      <task name="Task_WriteRequest">

         <controller>

            <variable name="dayCount" access="read,write,required"></variable>

         </controller>

         <assignment class="com.myrequest.task.WriteRequestAssignmentHandler"></assignment>

      </task>

      <transition name="Tr_WriteLeave" to="Fork_request">

         <action name="Ac_WriteLeave" class="com.myrequest.action.WriteLeaveActionHandler"></action>

      </transition>

   </task-node>

   <fork name="Fork_request">

      <transition name="Tr_Cancel" to="TN_RequesterCancel"></transition>

      <transition name="Tr_Request" to="Deci_IsChiefHere">

         <action name="Ac_GetChiefState" class="com.myrequest.action.GetChiefStateActionHandler"></action>

      </transition>

   </fork>

   <decision name="Deci_IsChiefHere">

      <handler class="com.myrequest.decision.IsChiefHereDecisionHandler"/>

      <transition name="Tr_Chief" to="TN_ChiefDecide"></transition>

      <transition name="Tr_Boss" to="TN_BossDecide"></transition>

   </decision>

   <task-node name="TN_RequesterCancel">

      <task name="Task_CancelRequest">

         <assignment class="com.myrequest.task.CancelRequestAssignmentHandler"></assignment>

      </task>

      <transition name="Tr_RequestCancel" to="Join_Request">

         <action name="Ac_RequestCancel" class="com.myrequest.action.RequestCancelActionHandler"></action>

      </transition>

   </task-node>

   <task-node name="TN_ChiefDecide">

      <task name="Task_ChiefDecide">

         <assignment class="com.myrequest.task.ChiefDecideAssignmentHandler"></assignment>

      </task>

      <transition name="Tr_ChiefApprove" to="Deci_NeedBossDecide">

         <action name="Ac_ChiefApprove" class="com.myrequest.action.ChiefApproveActionHandler"></action>

      </transition>

      <transition name="Tr_ChiefNotApprove" to="Join_Request">

         <action name="Ac_ChiefNotApprove" class="com.myrequest.action.ChiefNotApproveActionHandler"></action>

      </transition>

   </task-node>

   <join name="Join_Request">

      <transition name="Tr_Join" to="Deci_DoSomething"></transition>

   </join>

   <decision name="Deci_NeedBossDecide">

      <handler class="com.myrequest.decision.NeedBossDecideDecisionHandler"/>     

      <transition name="Tr_Need" to="TN_BossDecide"></transition>

      <transition name="Tr_NotNeed" to="Join_Request">

         <action name="Ac_NotNeed" class="com.myrequest.action.NotNeedActionHandler"></action>

      </transition>

   </decision>

   <task-node name="TN_BossDecide">

      <task name="Task_BossDecide">

         <assignment class="com.myrequest.task.BossDecideAssignmentHandler"></assignment>

      </task>

      <transition name="Tr_BossApprove" to="Join_Request">

         <action name="Ac_BossApprove" class="com.myrequest.action.BossApproveActionHandler"></action>

      </transition>

      <transition name="Tr_BossNotApprove" to="Join_Request">

         <action name="Ac_BossNotApprove" class="com.myrequest.action.BossNotApproveActionHandler"></action>

      </transition>

   </task-node>

   <decision name="Deci_DoSomething">

       <handler class="com.myrequest.decision.DoSomethingDecisionHandler"/>

      <transition name="Tr_Approve" to="ES_Finished">

         <action name="Ac_Approve" class="com.myrequest.action.ApproveActionHandler"></action>

      </transition>

      <transition name="Tr_NotApprove" to="ES_Finished">

         <action name="Ac_NotApprove" class="com.myrequest.action.NotApproveActionHandler"></action>

      </transition>

      <transition name="Tr_Cancel" to="ES_Finished">

         <action name="Ac_Cancel" class="com.myrequest.action.CancelActionHandler"></action>

      </transition>

   </decision>

   <end-state name="ES_Finished">

                     <event type="node-enter">

                            <action name="Ac_Finished" class="com.myrequest.action.FinishedActionHandler"></action>

                     </event>

   </end-state>

</process-definition>

3.2. 流程图片

3.3. 说明

3.3.1.    命名规则

start-state的定义为SS_

end-state的定义为ES_

task-node的定义为TN_

fork的定义为Fork_

join的定义为Join_

task的定义为Task_

transition的定义为Tr_

action的定义为Ac_

3.3.2.    join节点类型

join结点Join_Request,采用的是Discriminator模式,即只要有一个fork发出的分支到达join,流程就可以向下进行。

Join共有三中模式:

l         默认的是所有fork发出的分支都到达,流程才向下进行;

l         第二种就是Discriminator模式,只要有一个fork发出的分支到达join,流程就可以向下进行;

l         第三种是设置当有n个分支到达之后,流程就可以向下进行。

jpdl语言在jbpm3.1版本中还不支持对第二,第三两种模式的设置。需要在流程实例化之后,来制定join的模式。具体如何实现,参见后面有关代码部分。

3.3.3.    申请状态

系统存在一个有关申请的状态。系统用流程变量RequestState来存储。共有五个状态。存储在com.myrequest.RequestState.java文件中。

在用户启动此请假流程,完成第一个填写请假申请的任务实例后,此状态置为REQUEST。在完成取消请求任务之后,状态改为CANCEL。等等,读者可以仔细阅读流程定义和对应的代码。

要说明的是所有的状态修改都在完成任务之后的边上执行Action中来修改的。这样虽然增加了很多Action,但是状态修改明确。但对于这样的简单问题,前台代码在实际任务完整之后,就可以调用修改此状态变量。此处将这些修改都放在Action里面,也有一定演示的含义。

3.3.4.    取消请假任务说明

fork节点后,产生两个并行分支。其中一个TN_RequestCancel任务节点包含一个Task_CancelRequest的任务。当此分支执行到这里时,会把这个任务分配给启动流程的用户。在流程没有结束的时候,如果用户执行这个任务,就表示用户要取消请假申请。在这个取消申请的任务会修改请假状态,在此任务结束后,就会首先到达join节点。表示取消了此次申请操作。

4.   代码说明

4.1. 代码包结构

本实例是在Eclipse3.1.2里面实现的。

src/java目录下面,有包:

l         com.myrequest

n         存放流程的公共信息,包括:

n         interface RequestState,用来存放请求状态的5种状态

n         interface RequestVariable 存放流程实例的变量名

l         com.myrequest.action

n         存放流程中所有的ActionHandler

n         共有12个类,每个类对应流程中一个action的代码。

l         com.myrequest.task

n         存放流程中所有的taskAssignmentHandler分配类

n         共有4个类,对应4task

l         com.myrequest.decision

n         存放流程中所有的decision节点的判断类

n         包括3个类,对应3Decision节点

src/test目录下面,有包:

l         com.myrequest

n         包含此流程的测试类 MyRequestProcessTest.java

processes目录下面,有:

l         流程定义文件夹MyRequest,包含:

n         Gpd.xml

n         Processdefinition.xml

n         Processimage.jpg

4.2. 流程代码说明

对流程中使用到的和Actiontaskdecision相关的类,以及测试类进行说明。

4.2.1.    Action代码说明

流程中的Action都使用指定类的形式来完成Action的操作。

例如:

<transition name="Tr_WriteLeave" to="Fork_request">

<action name="Ac_WriteLeave" class="com.myrequest.action.WriteLeaveActionHandler"></action>

</transition>

当流程执行到边Tr_WriteLeave后,就会自动去执行Action里面指定的类WriteLeaveActionHandler

WriteLeaveActionHandler实现了接口ActionHandler。此接口就一个函数

public void execute(ExecutionContext executionContext)

当流程执行这个类的时候,就会去调用这个函数。所以我们Action所要完成的工作也都写在这个函数中。

RequestBeginActionHandler.execute()中,我们只做了将整个流程的请求状态设置为REQEUST状态。

4.2.2.    Task代码说明

所有Task都是在Task-node中描述的。Task都是人工任务,也就是说需要先将task分配给那个人,然后由这个人来完成。在人开始任务的时候,可以调用taskInstancestart()操作,表明任务开始。(start()操作是可选的,也可以不调用) 在任务结束后,可以调用taskInstanceend()操作,表示任务实例结束。如果是整个task-node中的最后一个taskend()操作,那么就会这个end操作就会触发流程继续向下走。

例如:

<task name="Task_WriteRequest">

<controller>

   <variable name="dayCount" access="read,write,required"></variable>

</controller>

<assignment class="com.myrequest.task.WriteRequestAssignmentHandler"></assignment>

</task>

当流程实例化Task_WriteRequest这个task后,首先会使用WriteRequestAssignmentHandler类来进行任务的分配。WriteRequestAssignmentHandler类实现了AssignmentHandler接口,包含一个
public void assign(Assignable assignable, ExecutionContext executionContext)
接口。其中参数assignable就是此任务实例的引用(TaskInstance实现了Assignable接口)。因此我们只要调用assignable.setActorId(String userid),就可以把这个任务分配给userid所代表的用户来执行了。

    对于前台,当用户登陆后,通过org.jbpm.db.TaskMgmtSession.findTaskInstances(java.lang.String actorId)就可以得到当前用户所有要执行的任务了,这里的任务是属于多个不同的流程实例的。如果查看数据库就会发现在jbpm_taskInstance表中,每一个taskinstanceactorId_字段记录的是次任务实例分配人员的用户名。这也就是上面所说的findTaskInstances方法如何工作的关键之处。
WriteRequestAssignmentHandler .assign()里面,我们首先读入流程变量userId,然后将任务分配给这个userId
    如何调用任务实例的end,来表示任务实例的完成呢?真正的系统中应该是在前台,当客户通过web或是客户端触发操作,然后执行对应的任务实例的end操作。但是在我们的代码中只能通过在junit的测试代码中来模拟。具体参见后面讲解测试代码部分。

4.2.3.    Decision代码说明

Decision节点可以有多种方式来进行条件判断。

方法一是在每条出边上加一个beanshell的表达式,jbpm引擎会按照流程定义文档中边的顺序一次调用,来判断那个表达式为true,当发现第一个为true的时候,流程就走这条边了。

方法二就是对Decision节点配置一个handler。通过一个类来实现条件判断。

 

例如:

   <decision name="Deci_IsChiefHere">

      <handler class="com.myrequest.decision.IsChiefHereDecisionHandler"/>

      <transition name="Tr_Chief" to="TN_ChiefDecide"></transition>

      <transition name="Tr_Boss" to="TN_BossDecide"></transition>

   </decision>

表示当执行到Deci_IsChiefHere节点后,会自动执行IsChiefHereDecisionHandler类。IsChiefHereDecisionHandler实现了DecisionHandler接口。此接口包含一个方法public String decide(ExecutionContext executionContext) throws Exception 这个方法应该返回一个transitionname,表示选择走那条边。

IsChiefHereDecisionHandler.decide()操作中,我们首先读取流程变量isChiefHere,然后判断走那条边。

4.3. 流程测试类代码说明

4.3.1.    测试类整体说明

测试类为com.myrequest. MyRequestProcessTest

包含三个设施函数,分别为

l         test14DayAndBossNotApprove()

n         测试员工申请14天假期,部门主管批准,但老板不批准

l         test4DayAndChiefApprove()

n         测试员工申请14天假期,部门主管批准,(不需要老板批准)

l         test14DayAndChiefApproveAndUserCancel()

n         测试员工申请14天假期,部门主管批准,在老板审批前员工自己撤销申请

 

每个测试前都执行setUp()操作,这个操作用来设置流程中两个变量,一个是用户id,另外一个是部门主管是否在岗的状态。可以修改这两个参数,进行不同的测试。尤其是第二个参数,会影响流程的走向,可以分别设置为truefalse以观察流程的走向和结果。

 

在测试类中还有7个辅助函数。分别为:

l         deployProcessDefinition()

n         流程部署

l         createProcessInstance()

n         创建流程实例

n         设置join节点的性质为Discriminator模式(参见流程定义部分)

n         设置流程相关变量

n         启动流程

l         userWriteRequest(int daycount)

n         模拟申请员工完成Task_WriteRequest任务,参数为请假的天数

l         chiefDecide(boolean isApprove)

n         模拟部门主管完成Task_ ChiefDecide任务,参数为部门主管是否批准

l         bossDecide(boolean isApprove)

n         模拟老板完成Task_ BossDecide任务,参数为老板是否批准

l         userCancel()

n         模拟申请员工完成Task_ CancelRequest任务

l         checkTasks()

n         检查整个流程中所有的任务的相关信息

4.3.2.    checkTasks()说明

checkTasks()的核心是pi.getTaskMgmtInstance().getTaskInstances();返回流程实例的所有任务实例列表。但要说明的是所返回的任务实例列表和当前流程执行的位置有关,在流程开始处,流程执行中间,和流程执行结束处调用得到的任务实例列表不同。列表包含已经完成的任务实例和当前任务实例。

本来在此方法中还有现实每个任务实例起始时间和结束时间的操作。但发现返回全部为null。这说明在没有使用数据库是,有关时间的属性是不可用的。也就是说,这些时间属性都是记录在数据库jbpm_taskInstance表中的。没有用数据库自然就得不到,它不会保留在内存的流程实例中。要说明的是jbpm_taskInstance表的start字段如果有时间,表示任务实例已经开始执行。如果end字段有时间,表示任务实例已经结束,此任务已经完成。

4.3.3.    测试中有关任务实例获取的说明

因为没有使用数据库,所以不能使用org.jbpm.db.TaskMgmtSession.findTaskInstances(java.lang.String actorId)得到不同用户的当前任务。所以只能便利当前流程实例任务列表,从中找到相应的任务来操作。

如果有人有更好的办法,请留言告知,谢谢:)

5.   实例不足和待学习地方

5.1. 不足

没有使用swimlane,可以添加swimlane来进行任务分配。像websale就是有角色的。主要的原因是对swimlane的使用我还没有搞清楚。我有关swimlane的学习心得会在下一篇对jbpm自带实例websale的分析中来描述。

5.2. 学习点

    通过这个实例,我发现org.jbpm.module.exe.ModuleInstance类有很多创建任务实例的方法,例如createTaskInstance(Task task)等等。我一直认为任务的实例化是在流程执行过程中,又工作流引擎来做的工作。不知道在我们写的代码中需要用到创建流程这样的方法吗?如果要用的话,在什么情况下会用到呢?
       
       此外还有很多问题没有搞懂,比如