JBPM用戶指南翻譯:第3章 指南

 
第3章 指南
這個指南將向你展示如何用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 下載概述”中找到這個工具的下載說明,但是完成本指南不需要圖形化設計器工具。
3.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());
}
 
3.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();
    }
 }
}
 
3.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"));
 
3.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());
}
 
3.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);
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章