jBPM開發入門指南(2) (2007-11-22 10:42)

這是一個已做好的示例,接下來我們將仿造這個實例來開發一個請假流程。

4 數據庫初始化

 jBPM 需要數據庫支持, jBPM 會把自己的一個初始化數據存儲到數據庫,同時工作流的數據也是存儲到數據庫中的。 jBPM 使用 Hibernate 來做爲自己的存儲層,因此只要是 Hibernate 支持的數據庫, jBPM 也就支持。

本文先以 MySQL 爲例,然後再以 Oracle 爲例,來談談 jBPM 的數據庫初始化操作。

注:在上面的 JBoss 自帶的示例中,並沒有設置數據庫,那是因爲 jBPM 默認使用的是內存數據庫 hsqldb 。

4.1 MySQL

1 、首先安裝 MySQL 。

MySQL 的安裝比較簡單,網上也有很多文章,本文不再贅述。本指南所用 MySQL 版本爲 MySQL 4.1 ( for windows )。再找一個 MySQL 客戶端,目的是方便查看數據庫中的數據,本文推薦使用 MySQL 網站上免費提供的“ MySQL Query Brower ”,當然你用其他的客戶端也行,比如 MySQL-Front 。

2 、建庫

MySQL 中創建一個庫,庫名: jbpm

3 、生成建表的 SQL 語句並建表

將 jbpm-starters-kit-3.1.1 下的子目錄 jbpm 改名爲 jbpm.3 ,否則在執行下面的 ant 命令時會報如 jbpm.3 目錄不存在的錯誤:

D:\jbpm-starters-kit-3.1.1\jbpm-db\build.xml:361: The following error occurred while executing this line:

D:\jbpm-starters-kit-3.1.1\jbpm-db\build.xml:68: Basedir D:\jbpm-starters-kit-3.1.1\jbpm.3 does not exist

在 DOS 窗下,進入 D:\jbpm-starters-kit-3.1.1\jbpm-db 目錄,執行如下命令:

ant mysql.scripts

執行成功後,在 D:\jbpm-starters-kit-3.1.1\jbpm-db\build\mysql\scripts 目錄裏生成了四個 sql 文件,它們做什麼用的一看名字就知道了。在 MySQL 客戶端中執行“ mysql.create.sql ”腳本,這樣將在 jbpm 庫中創建一個個的數據表。

4.2 Oracle
 先安裝好 Oracle 服務器。我們公司有現存的 Oracle 服務器,也提供給了我一個屬於我自己的用戶名,一登錄就可以任意在我的庫之下創建表了。所以這一步就省了,沒有的自個先裝好吧。

訪問 Oracle 推薦用“ PLSQL Developer ”。不過要連接 Oracle 還要在本機上裝上 Oracle 自己的客戶端程序,裏面提供了 JDBC 包和一些配置。要連接服務器還得配置一下,我一般都是不用 GUI 而直接改 tnsnames.ora 文件,在我的電腦裏此文件的目錄地址是: D:\oracle\ora92\network\ADMIN\tnsnames.ora ,內容如下 ( 兩面有兩個配置了 ) :

# TNSNAMES.ORA Network Configuration File: E:\oracle\ora92\network\admin\tnsnames.ora
# Generated by Oracle configuration tools.
WXXRDB_192.168.3.2 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.3.2)(PORT = 1521))
)
(CONNECT_DATA =
(SID = wxxrDB)
(SERVER = DEDICATED)
)
)

WXXRDB_192.168.4.2 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.4.2)(PORT = 1521))
)
(CONNECT_DATA =
(SID = wxxrDB)
(SERVER = DEDICATED)
)
)

前面都是 Oracle 的一些知識,不會的 Google 一下吧。最後配置好後,用 PLSQL Developer 輸入你的用戶名和密碼聯接到 Oracle ,就算 OK 了。

參考上面 MySQL 的步驟,基本一樣:

(1) 將 jbpm 改名爲 jbpm.3

(2) 再執行 ant oracle.scripts

(3) 用 jbpm-db\build\oracle\scripts 目錄的 oracle.create.sql 腳本,在 Oracle 中生成 jBPM 的所有表。在“ PLSQL Developer ”中可以新建一個 Command Windows 窗口然後輸入命令: @D:\jbpm-starters-kit-3.1.1\jbpm-db\build\oracle\scripts\oracle.create.sql

5  安裝 jBPM 的 Eclipse 開發插件

有個輔助工具開發起來方便一點,只不過現在 jBPM 的開發工具插件功能還不算太強,也就一個“項目創建嚮導”的功能,讓你:

(1)不用再去配置 classpath 庫的引用了

(2)直接得到了一個 jBPM 的項目初始結構

其實吧,開發 jBPM 也不需要什麼插件工具,在熟練了以後,庫引用了項目初始結構都可以手工創建。

插件不用再去下載了, jbpm-starters-kit-3.1.1 包裏就有,目錄地址如下: D:\jbpm-starters-kit-3.1.1\jbpm-designer\jbpm-gpd-feature\eclipse ,插件的安裝方式是鏈接式還是直接複製式,任選吧。不懂的就去看看《 Eclipse 從入門精通》這本書,在前面章節都有講到。另外,註明一下 Eclipse 的版本我是用 3.2 ,插件和 Eclispe 版本相關的,要注意了。

如果安裝成功,則 Eclipse 首選項裏多了一個 JBoss jBPM ,另外我們也需要到這個 jBPM 的首選項裏做一些配置工作――指定 jBPM 的安裝路徑(如下圖所示)。這個配置主要是爲了找到 jbpm 下的各種 jar 包,好讓 Eclipse 設置項目的庫引用。本文指向路徑是 d:\jbpm-starters-kit-3.1.1\jbpm.3


 6   jBPM 的 Hello World

 6.1 新建jBPM項目
 主菜單“文件->新建->項目”,在彈出的對話框裏,有“ Process Project ”項,如下圖所示:


 選上好,單擊“下一步”,起個名“ myjbpm ”,然後就可以單擊“完成”了。然後就生成了如下圖所示的一個項目結構:
 
 這個項目和通常 Eclipse 的項目結構有點不同,不過這是一個現在非常流行的項目結構, src/java 存放源文件, test/java 存放相應的 JUnit 單元測試代碼。如果你用 Maven 來編譯構建項目,對這種目錄結構一定不陌生。

項目創建起了,介紹一下里面的文件吧:

  • MessageActionHandler ,自動生成的一個 ActionHandler 。不想要可以刪掉。
  • ehcache.xml cache 的配置文件,裏面有很詳解的英文說明。沒有必要可以不用改它。
  • hibernate.cfg.xml   jBPM 是用 Hibernate 進行工作流的數據存儲的,這個就是 Hibernate 的配置文件。後面我們將講到如何配置這個文件。
  • jbpm.cfg.xml   jbpm 本身的配置文件。現在是空的,它用的是缺省配置,你想知道有哪些配置就去看這個文件 D:\jbpm-starters-kit-3.1.1\jbpm.3\src\java.jbpm\org\jbpm\default.jbpm.cfg.xml
  • log4j.properties  這個是日誌 API 包 log4j 的配置文件,用過 log4j 的都知道。
  • SimpleProcessTest.java  這個是對最重要的流程配置文件的 processdefinition.xml 單元測試代碼。這裏表揚一點, jBPM 的優良設計使得它的可測試性非常之高,喜歡寫 t 單元測試的人有福了。
  • gpd.xml  用於生成流程圖的定義文件。都是一些方框的座標和長寬
  • processdefinition.xml  這個是對最重要的流程配置文件,以後寫流程要經常和它打交道。
  • processimage.jpg  一個流程圖
 從項目結構來看,我們沒有看到 JSP 網頁程序,也沒有看到 GUI 客戶端程序,這些代碼都是要我們以後開發中來寫的。但本文不準備用 JSP 、 GUI ( Swing 、 SWT )來做示例,而是用 JUnit 代碼來做使用 jBPM 客戶端來演示。因爲 jBPM 實際上是一個後臺框架,至於前臺是 JSP 還是 Swing 還是無界面的 java.class 都是無關緊要的。在教程裏用無界面的 java.class 來做客戶端則更方便一些,如果進一步採用 JUnit ,則這樣的 java.class 同時還具備了單元測試的功能。以後就是用 JSP 寫了 WEB 頁面,我們還是可以用這些 JUnit 程序來做單元測試,避免了頻繁的鼠標點按 WEB 頁面這樣的力氣活。所以在 jBPM 自帶的英文教程裏都是一個 JUnit 程序,不仔佃看還真摸不着頭腦。

6.2 修改hibernate.cfg.xml
 hibernate.cfg.xml 的默認設置是用 HSQL ,這是一個內存數據庫,這種內存數據庫用來代替項目實際所用的數據庫來做單元測試挺不錯的。不過我們這裏是要試試用 MySQL 、 Oracle ,那就改一下設置吧。

注:配置值可參考 D:\jbpm-starters-kit-3.1.1\jbpm-db 對應子目錄下的 hibernate.properties 文件。

1 、 MySQL 的更改如下:

<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/jbpm</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>

2 、 Oracle 的更改如下:

<property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
<property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="hibernate.connection.url">jdbc:oracle:thin:@192.168.123.10:1521:wxxrDB</property>
<property name="hibernate.connection.username">chengang</property>
<property name="hibernate.connection.password">chengang</property>

如果你裝了 Oracle 的客戶端,並且 D:\oracle\ora92\network\ADMIN\tnsnames.ora 裏做了如下的設置

WXXRDB_192.168.123.10 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.123.10)(PORT = 1521))
)
(CONNECT_DATA =
(SID = wxxrDB)
(SERVER = DEDICATED)
則 Oracle 的 hibernate.connection.url 項也可以設爲: jdbc:oracle:oci:@WXXRDB_192.168.123.10

6.3 完善庫引用
 雖然 jBPM 在創建項目之初給我們設置好了庫引用,如下圖


 但後面運行時還是報一些 NoClassDefFoundError 異常,如沒有對 hibernate3.jar 的引用導致下面的錯誤

java.lang.NoClassDefFoundError: org/hibernate/Session

at org.jbpm.persistence.db.DbPersistenceServiceFactory.openService(DbPersistenceServiceFactory.java:55)

at org.jbpm.svc.Services.getService(Services.java:136)

.......

所以我們要爲本文的實例完善庫引用。主要是把 MySQL 和 Oracle 的 JDBC 庫、以及 Hibernate 的 hibernate3.jar 加入到項目的庫引用中。

(1) 找到缺少的 jar 包

  • mysql 的 jdbc 包,在 D:\jbpm-starters-kit-3.1.1\jbpm-db\mysql\lib 目錄裏
  • oracle 的 jdbc 包, jbmp 中沒有包含(可能是沒拿到 oracle 授權),我們可以自已去 oracle 網站上下載,或者去 oracle 安裝目錄 D:\oracle\ora92\jdbc\lib 找 ojdbc14.jar (我們公司用的是 Oracle9i )
  • Hibernate3.jar 在目錄 D:\jbpm-starters-kit-3.1.1\jbpm.3\lib\hibernate 裏。

(2) 在項目裏創建一個 lib 目錄,將這三個 jar 複製到 lib 目錄。

(3) 如下圖設置三 jar 包的庫引用


 6.4 開始HellorWorld

 這裏是一個很簡單的請假流程,請假人提交假單給經理審批,經理審批後結束。要說明的是,這個流程並不嚴謹,比如經理不通過流程應該到哪?不過這並不防礙拿它來做示例,螃蟹還得一個一個的吃。我們先拿這一杆子捅到底的流程做一個最簡單的示例,從整體上對 jBPM 工作流開發有概念先。然後我們再慢慢豐富。

1 、定義流程

流程的定義文件是 processdefinition.xml ,這個是一個關鍵文件, jBPM 的很大一部份內容都是關於它的。在這裏我們把原來自動生成的內容,稍做改動:

<?xml version="1.0" encoding="GBK"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="helloworld">
<!-- 申請 -->
<start-state name="request">
<task>
<controller>
<variable name="name" />
<variable name="day" />
<variable name="note" />
</controller>
</task>
<!-- 流程轉向 -->
<transition name="to_confirm" to="confirm">
<action name="requestAction"
class ="cn.com.chengang.jbpm.RequestAction">
<reason> 我要請假 </reason>
</action>
</transition>
</start-state>
<!-- 審批 -->
<state name="confirm">
<transition name="to_end" to="end">
<action name="finishAction"
class ="cn.com.chengang.jbpm.ConfirmAction" />
</transition>
</state>
<!-- 結束 -->
<end-state name="end" />
</process-definition>

說明:

流程的名稱改成了 helloworld 。(呵呵,也就是這裏和 helloworld 有關了)

<controller> 標籤定義了三個數據:姓名、請假天數、說明。

<transition> 標籤定了 request 節點的一個流程轉向,這裏是轉到 confirm 節點。

<action> 標籤定義了流程由一個節點轉到另一個節點時,所要執行的動作,動作封裝在一個 ActionHandler 類中。比如這裏當 request 到 confirm 結點時將執行 RequestAction 類的 execute 方法。

FinishAction 下面還有一個 <reason> (請假理由),它對應於 FinshAction 的屬性 String reason 。

2 、 編寫 ActionHandler

在上面 processdefinition.xml 裏我們定義了兩個 ActionHandler : RequestAction 、 ConfirmAction 。其代碼如下:

package cn.com.chengang.jbpm;
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;
public class RequestAction implements ActionHandler {
private static final long serialVersionUID = 1L;
private String reason;
public String getReason() {
return reason;
}
public void setReason(String reason) {
this .reason = reason;
}
public void execute(ExecutionContext context) throws Exception {
context.getContextInstance().setVariable("note", reason);

}

}

 說明: ExecutionContext 是一個貫通流程的容器。它是個大寶箱,裏面啥玩意都有,後面將更深入的提到。這裏的 reasion 就是 processdefinition.xml 中的 ” 我要請假 ”

 package cn.com.chengang.jbpm;
 import org.jbpm.graph.def.ActionHandler;
 import org.jbpm.graph.exe.ExecutionContext;
 public class ConfirmAction implements ActionHandler {
 private static final long serialVersionUID = 1L;
 public void execute(ExecutionContext context) throws Exception {
 context.getContextInstance().setVariable("note", " 准假 " );
}
}


OK ,後臺的程序就算寫完了(前臺客戶端的程序還沒寫),下面開始部署。


6.5 部署processdefinition.xml
 我們要把 processdefinition.xml 的流程定義的數據部署到數據庫中,因爲 jBPM 在正式運行的時候不是去讀 processdefinition.xml 文件,而是去讀數據庫中的流程定義。 這裏寫了一個個 JUnit 程序來部署 processdefinition.xml ,當然你用普通的 Java Main 也可以。

 package com.sample;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import junit.framework.TestCase;
 import org.jbpm.JbpmConfiguration;
 import org.jbpm.JbpmContext;
 import org.jbpm.graph.def.ProcessDefinition;
 /**
 * 部署 processdefinition.xml
 *
 * @author chengang
 *
 */
 public class DeployProcessTest extends TestCase {
 /**
 * 在本方法執行完畢後,檢查 jbpm_processdefinition 表會多了一條記錄
 *
 * @throws FileNotFoundException
 */
 public void testDeployProcessDefinition() throws FileNotFoundException {
  // 從 jbpm.cfg.xml 取得 jbpm 的配置
 JbpmConfiguration config = JbpmConfiguration.getInstance();
 // 創建一個 jbpm 容器
 JbpmContext jbpmContext = config.createJbpmContext();
 // 由 processdefinition.xml 生成相對應的流程定義類 ProcessDefinition
 InputStream is = new FileInputStream("processes/simple/processdefinition.xml");
 ProcessDefinition processDefinition = ProcessDefinition.parseXmlInputStream(is);
 // 利用容器的方法將流程定義數據部署到數據庫上
 jbpmContext.deployProcessDefinition(processDefinition);
 // 關閉 jbpmContext
 jbpmContext.close();
 }
 }  

運行此程序,在控制檯打印了一些日誌,通過。如果出錯,仔佃閱讀出錯信息以判斷錯誤原因,並確定你按照前面兩節:“修改 hibernate.cfg.xml ”和“完善庫引用”的內容做好了設置。


6.6 從數據庫中的查看部署效果
 無論是 MySQL 還是 Oracle ,查詢 jbpm_processdefinition 表,你會發現多了一條記錄,如下圖 ( 以 PLSQL Developer 的顯示爲例 )



 依次檢查各表我們可以發現有如下變化:



 
 
 



 
 並由此簡單判斷出各表的作用,表中各字段的作用由字段名也能知曉一二。

jbpm_processdefinition 一個流程定義文件對應一條記錄,可記錄多個流程定義文件,可記錄一個流程定義文件的對個版本。
jbpm_action 記錄 ActionHandler 的對象實例(以名稱爲標識)
jbpm_delegation 記錄了 ActionHandler 全類名,以便於用反射方式來加載
jbpm_envent 它的 transition 引用了 Jbpm_transition 表的 id ,再看其它字段,估計此表是表示流程轉向事件的一個實例,或者是一個各表之間的聯接表。
jbpm_node 流程結點
jbpm_transition 流程的轉向定義
jbpm_variableaccess 流程中攜帶的變量。 ACCESS 字段是這些變量的讀寫權限

7 jBPM 的客戶端開發

 有了前面的 HelloWorld 後臺流程,我們就要開始客戶端程序了。正如前面提到的,本文不寫 JSP ,而改採用 JUnit 的形式,輸出則用 System.out.println 。舉一反三,知道在方法中輸入及用 println 輸出,在 JSP 和 SWING 等 GUI 界面還不是一樣嘛。


這個 JUnit 客戶端,我們就借用創建項目時自動生成的 SimpleProcessTest.java 了,改寫後如下:


package com.sample;
import junit.framework.TestCase;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.context.exe.ContextInstance;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;

public class SimpleProcessTest extends TestCase {

private JbpmConfiguration config = JbpmConfiguration.getInstance();
private JbpmContext ctx = config.createJbpmContext();
// helloworld 對應於 jbpm_processdefinition 表的 name 字段值,也即 processdefinition.xml 的 name
// 這個值得取比較耗時,實際項目裏最好和“數據庫的 JDBC 連接”一樣,讓它共享,不要頻繁打開關閉。
private ProcessDefinition processDefinition = ctx.getGraphSession().findLatestProcessDefinition("helloworld");

public void testNewRequest() {
long id = newRequest();
System.out.println("id=" + id);
checkNewRequest(id);
confirmRequest(id);
checkconfirmRequest(id);
ctx.close();// 關閉 jbpm 容器
}

/**
* 創建一個請假單
*
* @return
*/
private long newRequest() {
// 創建一個新流程
ProcessInstance pi = processDefinition.createProcessInstance();
// 取得流程的數據環境
ContextInstance ci = pi.getContextInstance();
// 創建一張請假單
ci.setVariable("name", " 陳剛 www.chengang.com.cn" );
ci.setVariable("day", 2);
assertEquals(null, ci.getVariable("note"));
// 請假申請結束,轉到下一個流程結點
pi.signal();
return pi.getId();
}

/**
* 檢查請假單的數據
*
* @param id
*/
private void checkNewRequest(long id) {
// 從數據庫提取原流程
ProcessInstance pi = ctx.loadProcessInstance(id);
// 取得流程的數據環境
ContextInstance ci = pi.getContextInstance();
// 創建一張請假單
assertEquals(" 陳剛 www.chengang.com.cn" , ci.getVariable("name"));
assertEquals(Integer.valueOf(2), ci.getVariable("day"));
assertEquals(" 我要請假 " , ci.getVariable("note"));

// 當前是結點爲 confirm
assertEquals(pi.getRootToken().getNode().getName(), "confirm");
// 流程還沒結束
assertFalse(pi.hasEnded());
}

/**
* 審批陳剛的請假申請
*
* @param id
*/
private void confirmRequest(long id) {
ProcessInstance pi = ctx.loadProcessInstance(id);
ContextInstance ci = pi.getContextInstance();
// 不通過
ci.setVariable("note", " 不準請假,繼續加班 " );
// 審批結束,到下一個流程結點
pi.signal();
}

private void checkConfirmRequest(long id) {
ProcessInstance pi = ctx.loadProcessInstance(id);
ContextInstance ci = pi.getContextInstance();
// ConfirmAction 類在 signal 後執行,所以覆蓋了經理的審批意見
assertEquals(" 准假 " , ci.getVariable("note"));

// 當前是結點爲 end
assertEquals(pi.getRootToken().getNode().getName(), "end");
// 流程結束了
assertTrue(pi.hasEnded());
}
}

這個例子還是很簡單,而且關鍵是缺少用戶、組、權限等工作流系統必須的東西,不過沒關係。下面的內容我將完成一個更完整的實例。

前篇說起要講在JBPM中實現用戶權限,但這段時間比較忙,一直拖着。這裏先把jbpm自帶的websale例子放上來,大夥先將就看看。這個例子我已經把它抽取了出去,網頁部份丟棄了。在看的時候主要看WebsaleTest.java,看看它的用戶權限是怎麼用的。

幾個重點:

 (1) jBPM沒有角色的概念!!! 很多人把ActorID理解成角色,那是相當錯誤的。jBPM只有參與者的概念,也就是ActorID,它可以是用戶ID,也可以是角色ID,也可以什麼都不是。你要自己去把用戶角色等東東和ActorID映射起來。不過一般我們把ActorID設置成角色,但jbpm websale卻是設置成用戶的。
 (2)用戶信息的入口是JbpmContext,裏面有一個setActorID方法。你用戶登錄時候,把一些用戶信息set進去。
 (3)泳道概念:你去看看UML中的帶泳道的活動圖,那就是象一個游泳池裏的泳道一樣。

安裝提示:
 <classpathentry kind="con" path="JBPM/jBPM 3.1.2"/>
 .classpath的這一句很可能和你電腦的設置不符,這是jbpm插件的一個設置,指向jbpm的HOME目錄,以便找到jbpm的jar包。你按照你電腦上的設置相應修改一下這一行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章