2011年2月18日 星期五

[Oracle BPM/SOA 實務] 客製化BPM前端應用程式

實務上就台灣客戶的使用習慣而言,幾乎都會要求不管使用任何的BPM軟體作為流程運作平台,前端的應用程式都需要量身打造,以整合進客戶所慣用的應用程式。個人認為,這樣的要求算是合情合理,企業內部的BPM規劃本身就應該走向共用單一平台的設計模式,也就是,理想狀態下可以讓多種的應用系統(無論是.NET或Java或C++所開發的),有需要內部流程管理,或跨系統流程應用串接時,都可以將流程部署至此平台,前端應用系統可以維持原先介面的操作性,只是在適當時機透過API或特定介面與BPM進行起案、取件...等等的動作即可。

BPM平台與外界系統架構簡單來看是這個樣子:

注意,Front-end有時也是back-end,反之亦然...

也因此,在BPM的架構中,個人覺得除了流程設計的彈性之外,對外的溝通是另外一個非常重要的主題。而對前端應用與後端系統整合的方式,又各有不同的方式,前端的強調在豐富方便的API提供人工作業的互動;後端的就需強調SOA架構下,和現有系統的整合能力了!

這篇的主題我們先注重在前端的API上。服用此主題之前,請先服用Oracle BPM基礎,以免噎到。

假設前端應用系統需要一個簡單的審核流程,如下BPMN所示:


在BPM平台之中,我們設計了一個流程,其中包含了兩個人工作業關卡,分別是起案所用的 "InitTask"、審核所用的 "ConfirmTask";個別交給兩種角色 "InitRole" 與 "ComfirmRole" 來負責。此流程之中我們只帶了一個資訊,其XML Schema如下:


<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:customhumantask="http://blogdemo/customhumantask"
            targetNamespace="http://blogdemo/customhumantask"
            elementFormDefault="qualified">
  <xsd:element name="hello">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="helloString" type="xsd:string"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>


簡單來說,就是帶了一個hello的XML資料,其中內容為helloString的字串。

Oracle BPM提供了Human Workflow Service模組用以處理人工作業,其對外的介面有兩種:1) 如果你使用Java語言做為前端的開發,建議直接使用Java API操作人工作業,Java API底層會透過Local/Remote EJB或SOAP的方式連接BPM Server,也就是你的應用程式並不需要與BPM Server綁定在同一個container之上;2) 如果你使用的不是Java語言,唯一的一條路就是直接透過Web Service以SOAP的方式連接BPM Server,也因為是SOAP,你可以應用各種不同的語言開發前端,不需綁定Java語言。

本篇我們將先介紹使用Java語言開發的方式。

開始之前我們先看一下這樣的流程到底有那些與前端應用程式的互動點:

  1. InitRole 起始流程。
  2. InitRole 查詢起案人工單,填入詳細資料,完成關卡。
  3. ConfirmRole 查詢流到自身的人工單,取出詳細資料,更改審核結果,完成關卡。

檢視上述的整合點,我們將會用到Oracle BPM API中的createProcessInstance()queryTasks()getTaskDetailsByID()以及updateTaskOutcome()...等功能。


1) 準備開發環境與library:

首先,你會需要如下的JAR file在你的project之中
  • <MW_HOME>/wlserver_10.3/server/lib/wlfullclient.jar
  • <MW_HOME>/oracle_common/webservices/wsclient_extended.jar
  • <MW_HOME>/oracle_common/modules/oracle.xdk_11.1.0/xml.jar
  • <MW_HOME>/oracle_common/modules/oracle.xdk_11.1.0/xmlparserv2.jar
  • <MW_HOME>/Oracle_SOA/soa/modules/oracle.soa.fabric_11.1.1/bpm-infra.jar
  • <MW_HOME>/Oracle_SOA/soa/modules/oracle.soa.fabric_11.1.1/fabric-runtime.jar
  • <MW_HOME>/Oracle_SOA/soa/modules/oracle.soa.workflow_11.1.1/bpm-services.jar
  • <MW_HOME>/Oracle_SOA/soa/modules/oracle.bpm.client_11.1.1/oracle.bpm.bpm-services.client.jar
其中,熟悉WebLogic的人應該已經知道,wlfullclient.jar需要自己產生,你可以使用下列指令產生:

> cd <MW_HOME>/wlserver_10.3/server/lib
> java -jar ../../../modules/com.bea.core.jarbuilder_1.3.0.0.jar


你可以選用任何熟悉的IDE做為開發環境。


2) 初始化BPM Server連接:

// 指定連線參數與產生context物件
Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String> properties = new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String>();
      properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE, WorkflowServiceClientFactory.REMOTE_CLIENT);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL, "t3://<your_hostname>:<your_port>");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS, "<admin_password>");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL, "<admin_user>");


// 產生需要的IBPMServiceClient與IWorkflowServiceClient物件
BPMServiceClientFactory bpmServiceClientFactory = BPMServiceClientFactory.getInstance(properties, null, null);
IBPMServiceClient bpmSc = bpmServiceClientFactory.getBPMServiceClient();
IWorkflowServiceClient wfSc = bpmServiceClientFactory.getWorkflowServiceClient();


...


接下來你會需要取得遠端連線時,對於BPM Server的使用者認證、人工單查詢、人工單內容變更所需的Service物件:

IBPMUserAuthenticationService authSvc = bpmServiceClientFactory.getBPMUserAuthenticationService();
ITaskQueryService querySvc = wfSc.getTaskQueryService();
ITaskService taskSvc = wfSc.getTaskService(); 


...




3) 認證使用者

認證遠端連線使用者的資訊,並由此取得IBPMContex物件:


IBPMContext bpmCtx = authSvc.authenticate("tim", "welcome1".toCharArray(), null);


...



4) 起始流程

在起始流程之前,你必須確認該流程已經被成功部署於BPM Server之上,並且透過EM取得該部署流程的Composite相關資訊,如下圖所示,你可以取得該Composite ID為:blogdemo/CustomHumanTask!1.0*soa_87c431bf-7757-46de-8c52-55f4f2aabc11



而假設你的流程名稱是CustomHumanTaskProcess,則你產生新instance時所需要送入的ID就是:
blogdemo/CustomHumanTask!1.0*soa_87c431bf-7757-46de-8c52-55f4f2aabc11/CustomHumanTaskProcess


你可以透過下列API起始一個新的流程instance:


String instanceID = bpmSc.getInstanceManagementService().createProcessInstance(bpmCtx, "blogdemo/CustomHumanTask!1.0*soa_87c431bf-7757-46de-8c52-55f4f2aabc11/CustomHumanTaskProcess");


...




此API會回傳Composite instance的執行處理ID,你可以嘗試看看呼叫此API後,你可以在EM Console之中查詢到該流程的Composite instance ID已經正常出現。




5) 查詢起案人工單,填入詳細資料,完成關卡

現在流程應該是被啟動,並且執行至第一個關卡,等待 InitRole 角色的人將此取得該工作,填入相關資訊,並將工作單送出,讓流程繼續往下走。

首先,可以使用之前產生的querySvc查詢到使用者有權限的人工單基本資料:

// 指定查詢回傳資料
List queryColumns = new ArrayList();
queryColumns.add("TASKID");
queryColumns.add("TASKNUMBER");
queryColumns.add("TITLE");
queryColumns.add("PRIORITY");


// 指定回傳所有可用的ACTION
List<ITaskQueryService.OptionalInfo> optionalInfo = new ArrayList<ITaskQueryService.OptionalInfo>();
optionalInfo.add(ITaskQueryService.OptionalInfo.ALL_ACTIONS);


// 指定查詢條件 -- 此處只抓出已經ASSIGNED給特定角色的工作
Predicate pred = new Predicate(TableConstants.WFTASK_STATE_COLUMN, Predicate.OP_EQ, "ASSIGNED");


// 執行查詢
result = querySvc.queryTasks(bpmCtx, 
                             queryColumns, 
                             optionalInfo,                                    ITaskQueryService.AssignmentFilter.MY_AND_GROUP, // 抓出指定給該使用
                                                 //者本人或其所屬群組
                             null, // 不使用keywords
                             pred, 
                             null, // 沒有指定查詢排序
                             0,    // 不paging查詢結果
                             0);


...



接下來,我們把所要的Task物件詳細資訊取出,特別注意,Task物件的詳細內容一定要透過下列的API取得,之前queryTask所取出的資料並為包含Task payload等資訊:

// taskID可以透過前面query出的Task物件,呼叫
// task.getSystemAttributes().getTaskId()去取出taskId字串
Task task = querySvc.getTaskDetailsById(bpmCtx, taskId);

...



將我們所需要帶入的hello字串資訊帶入。記得嗎? 在Oracle BPM內部所處理的所有資料都會是XML格式,也因此,在產生hello物件,我們也會需要用標準W3C DOM的API來做 (底層是Oracle XML Parser V2)。

// 產生XML Element
Document document = XMLUtil.createDocument();
Element payload = document.createElementNS("http://xmlns.oracle.com/bpel/workflow/task", "payload");
Element hello = document.createElementNS("http://blogdemo/customhumantask", "hello");
Element helloString = document.createElementNS("http://blogdemo/customhumantask", "helloString");
helloString.appendChild(document.createTextNode("HELLO TIM!!"));
hello.appendChild(helloString);
payload.appendChild(hello);
document.appendChild(payload);

// 將XML資料指定給task做為其payload 
task.setPayloadAsElement(payload);

...


接下來,把給這個已經編輯完成的Task送出吧,並起告知流程往下走:

// "SUBMIT"字串是該HumanTask設計之時所指定可Outcome
taskSvc.updateTaskOutcome(bpmCtx, task, "SUBMIT");

...



6)  於每一個人工作業關卡,重複上述所的步驟

一樣的,這時候主管會透過authenticate()認證,連入BPM Server,透過queryTasks()查詢手頭的人工作業案件,透過getTaskDetailsByID()取出詳細payload資訊,接下來根據人工作業所設定的Outcome,呼叫updateTaskOutcome()將其review的結果指定為APPROVE或是REJECT。



Enjoy your BPM trip!!!







沒有留言: