程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用jBpm支持高級用戶交互模式

使用jBpm支持高級用戶交互模式

編輯:關於JAVA

許多通用業務流程都包含人類參與者。人類活動,從簡單場景(如人工批准) 到復雜場景(涉及復雜的數據輸入),在流程實現中引入了新的方面,如人類交 互模式。人類交互模式的一個典型集合包括:

四眼原則(The 4-eyes principle),通常又被稱為“職責分離”,它是決策 由多人彼此獨立作出時的一個常見場景。在很多情況下,很容易就能得到另一個 觀點/簽名。

任命(Nomination)是指上級根據團隊成員的任務安排、工作負荷或經驗人工 地將任務分配給他的情形。

任務通常被建模來表達一種預期:它們將在確定時間段內完成。如果任務沒有 按預期地進展,就需要一種上報(escalation)機制。兩種典型的上報實現是: 重新分配任務,並常常附帶一個上報已經發生的通知;或任務未按時完成的通知 (通常發給經理)。

鏈狀執行(Chained execution)是一個流程(片斷),其中的一系列步驟是 由同一個人來完成。

jBPM中的任務管理

jBPM的一個核心功能是為人類管理任務和任務列表。jBPM允許將任務和任務節 點作為整個流程設計的一部分使用。

任務一般在jBPM中定義成任務節點。單個任務節點可以包含一個或多個任務。 包含任務節點的jBPM流程的一個公共行為就是等待任務節點中的全部任務完成, 然後繼續執行。某個任務可被分配 給個人、用戶組或泳道:

假如任務被分配給某個特定用戶,那麼就只有這個使用者可以執行它。

假如任務被分配給某個用戶組,那麼這個組內的任何參與者都能執行這個任務 。jBPM使用的是參與者池(pooled actors)符號(它可以包含組名、組名列表和 參與者個人列表等),而不是組ID。如果用戶開始執行在他們組任務列表中的任 務,最終可能會引起沖突——可能有多人開始執行相同的任務。為了避免這種情 況,在開始執行任務之前,用戶應該將任務從組任務列表移動到他們自己的任務 列表中。

泳道代表一個流程角色,它通常被分配給一個用戶組。它是一種指定流程中的 多個任務要由同一參與者完成的機制。因此,在第一個任務被分配給某個泳道之 後,流程就會記住所有在相同泳道內的後續任務都將由同一參與者完成。

jBPM提供了兩種定義任務分配的基本方法:作為流程定義的一部分或通過編程 實現。如果是作為流程定義的一部分,分配可以通過指定具體用戶、用戶組或泳 道 完成。此外,可以使用表達式根據流程變量動態確定某個具體用戶。完整的編 程實現是基於分配處理器(assignment handler)的,它允許任務根據任意的計 算規則去查找用戶ID。

流程定義描述流程實例的方式類似任務描述任務實例的方式。當流程執行時, 一個流程實例——流程的運行時表示——就會被創建。類似,一個任務實例—— 任務的運行時表示——就會被創建。根據任務定義,任務實例被分配給一個參與 者/參與者組。

任務實例的一個作用就是支持用戶交互——把數據顯示給用戶並從用戶那裡收 集數據。一個jBPM任務實例擁有訪問流程(令牌)變量的全部權限,而且還可以 有自己的變量。任務能夠擁有自己的變量對於以下場景非常有用:

在任務實例中創建流程變量的副本,這樣對任務實例變量的即時更新只有在該 任務完成且這些副本被提交給流程變量時才會影響流程變量。

創建更好支持用戶活動的“派生(計算)”變量。

任務自己的變量在jBPM中是通過任務控制器處理器(task controller handler)支持的,它可以在任務實例創建時生成任務實例數據(從流程數據), 並在任務實例完成時將任務實例數據提交給流程變量。

實現四眼原則

我們上面已經說過,實現四眼原則意味著要允許多人同時干一個活。它的實現 有以下幾種可能方法:

在任務外解決:需要大量時間的任務並行循環(parallel looping) 。

使用動作處理器(Action handler):附加到任務節點的進入事件(enter event),基於流程實例變量創建多個節點實例。

在任務內解決:引入“任務接受量(task take)”(類似jPDL 4)並允許某 個任務實例可被接受多次。

根據jBPM最佳實踐 ——“擴展jBPM API而不是去搞復雜的流程建模” ,我決 定采用任務內解決的方法。這就要求修改jBPM提供的任務和任務實例類。

擴展Task類

jBPM任務的定義被包含在org.jbpm.taskmgmt.def.Task類中。為了支持四眼原 則,我們需要給類增加以下的字段/方法(清單1):

protected int numSignatures = 1;

  public int getNumSignatures(){
   return numSignatures;
  }
  public void setNumSignatures(int numSignatures){
   this.numSignatures = numSignatures;
  }

清單1 給Task類增加字段和方法

這個新的參數允許指定任務完成所需的任務處理人數量。缺省值為1,這意味 著,只有1個用戶應該/可以處理這個任務。

jBPM使用Hibernate來向數據庫保存和讀取數據。為了讓我們新加的變量持久 化,我們需要更新Task類的Hibernate配置文件(Task.hbm.xml),它在 org.jbpm.taskmgmt.def文件夾中,增加代碼如下(清單2)

<property name="numSignatures"  column="NUMSIGNATURES_" />

清單2 在Task映射文件中指定新增域

為了讓我們新加的屬性能被流程定義和數據庫正確讀取,我們需要修改 org.jbpm.jpdl.xml.JpdlXmlReader類以正確地讀取我們的新屬性(清單3)

String numSignatureText = taskElement.attributeValue ("numSignatures");
if (numSignatureText != null) {
   try{
    task.setNumSignatures(Integer.parseInt(numSignatureText));
   }
   catch(Exception e){}
}

清單3 讀取numSignature屬性

最後,因為JpdlXmlReader根據模式來驗證XML,因此我們需要在jpdl-3.2.xsd 中增加一個屬性定義(清單4):

<xs:element name="task">
  ………………….
    <xs:attribute name="numSignatures"  type="xs:string" />

清單4 在jpdl-3.2.xsd中增加numSignatures屬性

當完成這些工作,任務定義就被擴展可以使用numSignatures屬性(清單5):

<task name="task2" numSignatures = "2">
  <assignment pooled-actors="Peter, John"></assignment>
  </task>

清單5 給任務定義增加numSignatures屬性

擴展TaskInstance類

在擴展完任務類後,我們還需要創建一個自定義的任務實例類來跟蹤分配給該 任務實例的參與者,並確保所有被分配的參與者完成類執行(清單6)。

package com.navteq.jbpm.extensions;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.jbpm.JbpmException;
import org.jbpm.taskmgmt.exe.TaskInstance;

public class AssignableTaskInstance extends TaskInstance  {

  private static final long serialVersionUID = 1L;

  private List<Assignee> assignees = new  LinkedList<Assignee>();

  private String getAssigneeIDs(){
  StringBuffer sb = new StringBuffer();
  boolean first = true;
  for(Assignee a : assignees){
   if(!first)
   sb.append(" ");
   else
   first = false;
   sb.append(a.getUserID());
  }
  return sb.toString();
  }

  public List<Assignee> getAssignees() {
  return assignees;
  }

  public void reserve(String userID) throws JbpmException {

  if(task == null)
    throw new JbpmException("can't reserve instance with  no task");

  // Duplicate assignment is ok 
  for(Assignee a : assignees){
   if(userID.equals(a.getUserID()))
   return;
  }

  // Can we add one more guy? 

  if(task.getNumSignatures() > assignees.size()){
   assignees.add(new Assignee(userID));
   return;
  }

    throw new JbpmException("task is already reserved by  " +
getAssigneeIDs());

  }

  public void unreserve(String userID){

  for(Assignee a : assignees){
   if(userID.equals(a.getUserID())){
   assignees.remove(a);
   return;
   }
  }
  }

  private void completeTask(Assignee assignee, String  transition){

  assignee.setEndDate(new Date());

  // Calculate completed assignments
  int completed = 0;
  for(Assignee a : assignees){
   if(a.getEndDate() != null)
   completed ++;
  }
  if(completed < task.getNumSignatures())
   return;
  if(transition == null)
   end();
  else
   end(transition);
  }

  public void complete(String userID, String transition)  throws JbpmException{

  if(task == null)
    throw new JbpmException("can't complete instance with  no task");

  // make sure it was reserved 
  for(Assignee a : assignees){
   if(userID.equals(a.getUserID())){
   completeTask(a, transition);
   return;
   }
  }
    throw new JbpmException("task was not reserved by "  + userID);
  }

  public boolean isCompleted(){

  return (end != null);

  }
}

清單6 擴展TaskInstance類

這個實現擴展了jBPM提供的TaskInstance類,並跟蹤完成該實例所需的參與者 個數。它引入了幾個新方法,允許參與者預留(reserve)/退還(unreserve)任 務實例,以及讓指定參與者完成任務執行。

清單6的實現依賴一個支持類Assignee(清單7)

package com.navteq.jbpm.extensions;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Assignee implements Serializable{

  private static final long serialVersionUID = 1L;
  private static final DateFormat dateFormat = new
SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

  long id = 0;
  protected String startDate = null;
  protected String userID = null;
  protected String endDate = null;

  public Assignee(){}

  public Assignee(String uID){

  userID = uID;
     startDate = dateFormat.format(new Date());
  }

////////////Setters and Getters ///////////////////

  public long getId() {
  return id;
  }
  public void setId(long id) {
  this.id = id;
  }
  public String getStartDate() {
  return startDate;
  }
  public void setStartDate(String startDate) {
  this.startDate = startDate;
  }
  public String getUserID() {
  return userID;
  }
  public void setUserID(String id) {
  userID = id;
  }
  public String getEndDate() {
  return endDate;
  }
  public void setEndDate(String endDate) {
  this.endDate = endDate;
  }

  public void setEndDate(Date endDate) {
  this.endDate = dateFormat.format(endDate);
  }

  public void setEndDate() {
  this.endDate = dateFormat.format(new Date());
  }

  public String toString(){

  StringBuffer bf = new StringBuffer();
  bf.append(" Assigned to ");
  bf.append(userID);
  bf.append(" at ");
  bf.append(startDate);
  bf.append(" completed at ");
  bf.append(endDate);
  return bf.toString();
  }
}

清單7 Assignee類

自定義的TaskInstance類和Assignee類都必須保存到數據庫中。這意味著需要 給這兩個類實現Hibernate映射 (清單8,9):

<?xml version="1.0"?>
  <!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  <hibernate-mapping auto-import="false" default- access="field">
   <subclass  namename="com.navteq.jbpm.extensions.AssignableTaskInstance"
      extends="org.jbpm.taskmgmt.exe.TaskInstance"
      discriminator-value="A">
    <list name="assignees" cascade="all" >
     <key column="TASKINSTANCE_" />
     <index column="TASKINSTANCEINDEX_"/>
     <one-to-many class="com.navteq.jbpm.extensions.Assignee" />
    </list>
   </subclass>
  </hibernate-mapping>

清單8 自定義任務實例的Hibernate映射文件

<?xml version="1.0"?>
  <!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  <hibernate-mapping auto-import="false" default- access="field">
   <class name="com.navteq.jbpm.extensions.Assignee"
      table="JBPM_ASSIGNEE">
    <cache usage="nonstrict-read-write"/>
    <id name="id" column="ID_"><generator  class="native" /></id>
    <!-- Content -->
    <property name="startDate"   column="STARTDATE_" />
    <property name="userID"  column="USERID_" />
    <property name="endDate"   column="ENDDATE_" />
   </class>
  </hibernate-mapping>

清單9 Assignee類的Hibernate映射文件

要讓jBPM能夠使用我們的自定義任務實例實現,我們還需要提供一個自定義的 任務實例工廠(清單10)。

package com.navteq.jbpm.extensions;

import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.TaskInstanceFactory;
import org.jbpm.taskmgmt.exe.TaskInstance;

public class AssignableTaskInstanceFactory implements  TaskInstanceFactory {

  private static final long serialVersionUID = 1L;

  @Override
  public TaskInstance createTaskInstance(ExecutionContext  executionContext) {

  return new AssignableTaskInstance();
  }

}

清單10 自定義的任務實例工廠

最後,為了讓jBPM運行時使用正確的任務實例工廠(清單10),還必須創建一 個新的jBPM配置(清單11)。

<jbpm-configuration>
   <bean  name="jbpm.task.instance.factory"
class="com.navteq.jbpm.extensions.AssignableTaskInstanceFactory"  singleton="true"
/>
  </jbpm-configuration>

清單11 jBPM配置

完成所有這些變更之後(清單1-11),一個典型的任務處理顯示如下:

List<String> actorIds = new  LinkedList<String>();
actorIds.add("Peter");
List<TaskInstance> cTasks = jbpmContext.getGroupTaskList (actorIds)
TaskInstance cTask = cTasks.get(0);
AssignableTaskInstance aTask = (AssignableTaskInstance)cTask;
try{
  aTask.reserve("Peter");
  // Save
  jbpmContext.close();
}
catch(Exception e){
  System.out.println("Task " + cTask.getName() + " is already  reserved");
  e.printStackTrace();
}

清單12 處理可分配任務實例

這裡,在得到某個用戶的任務實例並將其轉變成可分配任務實例之後,我們將 試著預留它。一旦預留成功,我們將關閉jBPM運行時以提交事務。

實現任命

JBoss jBPM可以非常輕易的實現手動將任務分配給特定用戶。根據jBPM提供的 簡單API,可以完成將任務實例從一個任務列表移動到另一個任務列表,因此給某 個用戶分配任務相當直接(清單13)

List<String> actorIds = new  LinkedList<String>();
actorIds.add("admins");
String actorID = "admin";
List<TaskInstance> cTasks = jbpmContext.getGroupTaskList (actorIds);
TaskInstance cTask = cTasks.get(0);
cTask.setPooledActors((Set)null);
cTask.setActorId(actorID);

清單13 將任務重新分配給指定用戶

jBPM提供了2類不同的API來設置參與者池:一類接收字符串id數組,另一類則 接收id集合。如果要清空一個池,就要使用那個接收集合的API(傳入一個null集 合)。

實現上報

前面已經說過,上報一般被實現為任務的重新分配,並常常附帶一個上報已發 生的通知;或是實現成一個任務未及時完成的通知。

實現為重新分配的上報

盡管jBPM不直接支持上報,但它提供了2個基本的機制:超時和重新分配(參 見上節)。粗一看,實現上報只需將這二者結合即可,但是仔細一想還是存在一 些困難:

jBPM實現中的關系並不總是雙向的。如,從一個任務節點我們可以找到所有這 個節點定義的任務,但是從一個任務,並沒有API可以完成找到包含它的任務節點 的工作;由某個任務實例,你可以得到一個任務,但是沒有由某個任務得到所有 實例的API,諸如此類。

超時不是發生在任務自身,而是發生在任務節點上。由於某個節點可以關聯多 個任務,並且jBPM關系實現並不是雙向的(見上),因此要跟蹤當前任務實例就 需要其他的支持手段。

以重新分配實現的上報的整個實現涉及3個處理器:

負責給任務分配參與者的分配處理器。這個處理器跟蹤它是一個首次任務調用 還是一個上報任務調用。清單14給出了一個分配處理器的例子。

package com.sample.action;

import org.jbpm.graph.def.Node;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.def.AssignmentHandler;
import org.jbpm.taskmgmt.exe.Assignable;

public class EscalationAssignmentHandler implements  AssignmentHandler {

  private static final long serialVersionUID = 1L;

  @Override
  public void assign(Assignable assignable, ExecutionContext  context)
  throws Exception {

  Node task = context.getToken().getNode();
  if(task != null){
   String tName = task.getName();

   String vName = tName + "escLevel";
   Long escLevel = (Long)context.getVariable(vName);
   if(escLevel == null){
   // First time through
   assignable.setActorId("admin");
   }
   else{
   // Escalate
   assignable.setActorId("bob");
   }
  }
  }
}

清單14 分配處理器示例

這裡我們嘗試得到一個包含了給定任務上報次數的流程變量。如果變量未定義 ,則就分配“admin”為任務擁有者,否則任務就被分配給“bob”。在這個處理 器中可以使用任何其他的分配策略。

任務實例創建動作處理器(清單15),它保存流程實例上下文的任務實例 id

package com.sample.action;

import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.exe.TaskInstance;

public class TaskCreationActionHandler implements  ActionHandler {

  private static final long serialVersionUID = 1L;

  @Override
  public void execute(ExecutionContext context) throws  Exception {

  Node task = context.getToken().getNode();
  TaskInstance current = context.getTaskInstance();
  if((task == null) || (current == null))
   return;

  String tName = task.getName();
  String iName = tName + "instance";

  context.setVariable(iName, new Long(current.getId()));
  }

}

清單15 任務實例創建處理器

任務節點計時器觸發調用的超時處理器(清單16)。

package com.sample.action;

import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.def.GraphElement;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.exe.TaskInstance;

public class EscalationActionHandler implements ActionHandler  {

  private static final long serialVersionUID = 1L;

  private String escalation;

  @Override
  public void execute(ExecutionContext context) throws  Exception {

  GraphElement task = context.getTimer().getGraphElement ();
  if(task == null)
   return;

  String tName = task.getName();
  String vName = tName + "escLevel";
  long escLevel = (long)context.getVariable(vName);
  if(escLevel == null)
   escLevel = new long(1);
  else
   escLevel += 1;
  context.setVariable(vName, escLevel);
  String iName = tName + "instance";

  long taskInstanceId = (long)context.getVariable (iName);

  TaskInstance current = 
context.getJbpmContext().getTaskInstance(taskInstanceId);

  if(current != null){
   current.end(escalation);
  }
  }
}

清單16 超時處理器

這個處理器首先記錄上報計數器,接著完成此節點關聯的任務實例。任務實例 的完成伴隨有一個變遷(一般是回到任務節點)。

使用以上描述的處理器實現上報的簡單流程例子顯示在清單17中。

<?xml version="1.0" encoding="UTF-8"?>
  <process-definition
   xmlns="urn:jbpm.org:jpdl-3.2"
   name="escalationHumanTaskTest">
   <start-state name="start">
   <transition to="customTask"></transition>
  </start-state>
   <task-node name="customTask">
   <task name="task2">
   <assignment  class="com.sample.action.EscalationAssignmentHandler"><
/assignment>
   </task>
   <event type="task-create">
   <action name="Instance Tracking"  class="com.sample.action.TaskCreationActionHandler"></action>< BR>    </event>
   <timer duedate="10 second" name="Escalation timeout">
   <action class="com.sample.action.EscalationActionHandler">
    <escalation>
    escalation
    </escalation>
   </action>
   </timer>
   <transition to="end" name="to end"></transition>
   <transition to="customTask"  name="escalation"></transition>
  </task-node>
  <end-state name="end"></end-state>
  </process-definition>

清單17 簡單流程的上報

實現成通知的上報

jBPM為郵件傳遞提供了強大支持,這使得實現成通知的上報變得極其簡單。郵 件傳遞可由給節點附加定時器,然後觸發,它使用已經寫好的郵件動作來完成通 知傳遞。

實現鏈狀執行

鏈狀執行直接由jBPM泳道支持,並不需要額外的開發。

總結

不管我們在自動化方面投入多少努力,面對復雜的業務流程,總免不了要有人 工介入的可能。在這篇文章中,我給出了一系列已建立的高級人工交互模式,並 展示了用jBPM完成它是多麼輕而易舉。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved