Liferay7 BPM門戶開發之2: BPMN 2.0 規範入門 (Activiti BPMN extensions)
Liferay最大的問題是BPM弱,如果做企業開發,BPM必不可少,所以直入主題,做個BPMN2入門.
本文參考地址:http://activiti.org/userguide/index.html#bpmnConstructs
BPMN 2.0中的重要概念:
- Events 事件
- Sequence Flow 順序流
- Gateways 閘道器
- Tasks 任務
- Sub-Processes and Call Activities 子流程
- Transactions and Concurrency 事務併發
- Process Initiation Authorization 初始化認證
- Data objects 流程資料
其他相關項:
- Form properties 表單屬性
- External form rendering 外部表單整合
1、Events
1、1 Timer Event Definitions
由時間觸發的時間,用於
- start event
- intermediate event
- boundary event
必須有確切的一個元素,分別是:
timeDate
<timerEventDefinition><timeDate>2011-03-11T12:13:14</timeDate></timerEventDefinition>
在確切的時間點執行
timeDuration
<timerEventDefinition><timeDuration>P10D</timeDuration></timerEventDefinition>
從最後一個任務完成後10天開始執行
timeCycle
<timerEventDefinition>
<timeCycle activiti:endDate="2015-02-25T16:42:11+00:00">R3/PT10H</timeCycle>
</timerEventDefinition>
或者變數形式:
<timerEventDefinition>
</timerEventDefinition>
迴圈3次,間隔10小時
也可以使用cron expressions :http://www.quartz-scheduler.org/documentation/
1.2 Signal Event Definitions
一個例子:https://github.com/chanjarster/activiti-learn/wiki/%E8%AF%A6%E8%A7%A3signal%20event
1.3 Message Event Definitions
想象一下,作為一個嵌入式的流程引擎(不是國內很多固化Hardcode式的流程引擎),Activiti關心的是實際從第三方應用系統接收的訊息。這將是環境依賴和需要特定平臺的活動。
比如:
- 連線到JMS(java訊息服務)佇列
- 處理一個WebService
- REST請求
- MQ佇列的訊息處理
- XMPP訊息監聽
- ......
總之,訊息是和應用程式相關聯的。
在您收到您的應用程式中的一個訊息後,您必須決定該如何處理它。如果訊息應該觸發一個新的流程例項的開始,process instance的啟動不應該使用runtimeService.startProcessInstanceByKey
,在以下方法中選擇:
- ProcessInstance startProcessInstanceByMessage(String messageName);
- ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
- ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);
<definitions id="definitions" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples" xmlns:tns="Examples"> <message id="newInvoice" name="newInvoiceMessage" /> <message id="payment" name="paymentMessage" /> <process id="invoiceProcess"> <startEvent id="messageStart" > <messageEventDefinition messageRef="newInvoice" /> </startEvent> ... <intermediateCatchEvent id="paymentEvt" > <messageEventDefinition messageRef="payment" /> </intermediateCatchEvent> ... </process> </definitions>
有不同的方式來啟動事件,Message Event Definitions 就非常有用了
例如訂單可能來自call center ,也可以來自web shop
1.4 Start Events
開始事件總是捕捉型(Catching)的,比如一個訊息接收,比如一個時間觸發,總是有指定的觸發。
<startEvent id="request" activiti:initiator="initiator" />
啟動:
try { identityService.setAuthenticatedUserId("bono"); runtimeService.startProcessInstanceByKey("request"); } finally { identityService.setAuthenticatedUserId(null); }
1.5 None Start Event
一個無啟動事件在技術上意味著啟動過程例項的觸發器是未指定的。而且沒有子元素節點。
一個有啟動表單的例子:
<startEvent id="request" activiti:formKey="org/activiti/examples/taskforms/request.form" />
圖形是一個空心圓:
1.6 Timer Start Event
時間啟動事件,是一個時鐘在中間的圓:
1.7Boundary Events
邊界事件是catching型的,連線到一個活動(一個邊界事件永遠不會throwing)的事件。
這意味著,當活動正在執行時,事件正在偵聽某種型別的觸發器。當事件被捕獲時,該活動被中斷,順序流下行。
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport"> <timerEventDefinition> <timeDuration>PT4H</timeDuration> </timerEventDefinition> </boundaryEvent>
<boundaryEvent id="boundary" attachedToRef="task" cancelActivity="true"> <messageEventDefinition messageRef="newCustomerMessage"/> </boundaryEvent>
2.Sequence Flow
2.1Conditional sequence flow
帶有UEL條件表示式的順序流
<sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${order.price > 100 && order.price < 250}]]> </conditionExpression> </sequenceFlow>
2.2Default sequence flow
任務和閘道器都可以有預設順序流。
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" default="flow2" /> <sequenceFlow id="flow1" sourceRef="exclusiveGw" targetRef="task1"> <conditionExpression xsi:type="tFormalExpression">${conditionA}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="task2"/> <sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="task3"> <conditionExpression xsi:type="tFormalExpression">${conditionB}</conditionExpression> </sequenceFlow>
flow2就是預設順序流,當所有條件不滿足,則選擇預設順序流。
3 Gateways
3.1 Exclusive Gateway
異或閘道器
輸入:只要有一個活動節點到達該閘道器那麼就觸發
輸出:有多個輸出點時,只會觸發一個
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" /> <sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1"> <conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2"> <conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3"> <conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression> </sequenceFlow>
3.2Parallel Gateway
並行閘道器,可以有多個輸入和輸出(fork, join or both) ,實現AND邏輯
輸入:該閘道器所有的輸入節點都必須完成後才能觸發該閘道器
輸出:該閘道器的所有輸出接點都將觸發(除非轉移條件不通過)
<startEvent id="theStart" /> <sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" /> <parallelGateway id="fork" /> <sequenceFlow sourceRef="fork" targetRef="receivePayment" /> <sequenceFlow sourceRef="fork" targetRef="shipOrder" /> <userTask id="receivePayment" name="Receive Payment" /> <sequenceFlow sourceRef="receivePayment" targetRef="join" /> <userTask id="shipOrder" name="Ship Order" /> <sequenceFlow sourceRef="shipOrder" targetRef="join" /> <parallelGateway id="join" /> <sequenceFlow sourceRef="join" targetRef="archiveOrder" /> <userTask id="archiveOrder" name="Archive Order" /> <sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" /> <endEvent id="theEnd" />
3.3 Inclusive Gateway
Inclusive包容閘道器(Xor輸入,And輸出)
輸入:只要有一個活動節點到達該閘道器那麼就觸發該閘道器(同XOR輸入)
輸出:該閘道器的所有輸出接點都將觸發(除非轉移條件不通過)同AND輸出
<startEvent id="theStart" /> <sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" /> <inclusiveGateway id="fork" /> <sequenceFlow sourceRef="fork" targetRef="receivePayment" > <conditionExpression xsi:type="tFormalExpression">${paymentReceived == false}</conditionExpression> </sequenceFlow> <sequenceFlow sourceRef="fork" targetRef="shipOrder" > <conditionExpression xsi:type="tFormalExpression">${shipOrder == true}</conditionExpression> </sequenceFlow> <userTask id="receivePayment" name="Receive Payment" /> <sequenceFlow sourceRef="receivePayment" targetRef="join" /> <userTask id="shipOrder" name="Ship Order" /> <sequenceFlow sourceRef="shipOrder" targetRef="join" /> <inclusiveGateway id="join" /> <sequenceFlow sourceRef="join" targetRef="archiveOrder" /> <userTask id="archiveOrder" name="Archive Order" /> <sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" /> <endEvent id="theEnd" />
4. TASKS
4.1User Task
需要使用者參與的任務
humanPerformer方式,指定一個執行人
<userTask id='theTask' name='important task' > <humanPerformer> <resourceAssignmentExpression> <formalExpression>kermit</formalExpression> </resourceAssignmentExpression> </humanPerformer> </userTask>
potentialOwner 多個人或組
<userTask id='theTask' name='important task' > <potentialOwner> <resourceAssignmentExpression> <formalExpression>user(kermit), group(management)</formalExpression> </resourceAssignmentExpression> </potentialOwner> </userTask>
還有通過屬性來設定
- <userTask id="theTask" name="my task" activiti:assignee="kermit" />
- <userTask id="theTask" name="my task" activiti:candidateUsers="kermit, gonzo" />
- <userTask id="theTask" name="my task" activiti:candidateGroups="management, accountancy" />
4.2Script Task
略
4.3Java Service Task
4.4Business Rule Task
業務邏輯任務,使用JBoss Drools規則引擎來處理輸入輸出;
<process id="simpleBusinessRuleProcess"> <startEvent id="theStart" /> <sequenceFlow sourceRef="theStart" targetRef="businessRuleTask" /> <businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}" activiti:resultVariable="rulesOutput" /> <sequenceFlow sourceRef="businessRuleTask" targetRef="theEnd" /> <endEvent id="theEnd" /> </process>
4.5Camel Task
camel.apache 規則引擎任務,一個例子
<process id="PingPongProcess"> <startEvent id="start"/> <sequenceFlow id="flow1" sourceRef="start" targetRef="ping"/> <serviceTask id="ping" activiti:type="camel"/> <sequenceFlow id="flow2" sourceRef="ping" targetRef="saveOutput"/> <serviceTask id="saveOutput" activiti:class="org.activiti.camel.examples.pingPong.SaveOutput" /> <sequenceFlow id="flow3" sourceRef="saveOutput" targetRef="end"/> <endEvent id="end"/> </process>
org.activiti.camel.examples.pingPong.SaveOutput類
@Override public void configure() throws Exception { from("activiti:PingPongProcess:ping").transform().simple("${property.input} World"); }
測試程式碼:
@Deployment public void testPingPong() { Map<String, Object> variables = new HashMap<String, Object>(); variables.put("input", "Hello"); Map<String, String> outputMap = new HashMap<String, String>(); variables.put("outputMap", outputMap); runtimeService.startProcessInstanceByKey("PingPongProcess", variables); assertEquals(1, outputMap.size()); assertNotNull(outputMap.get("outputValue")); assertEquals("Hello World", outputMap.get("outputValue")); }
5. Sub-Processes and Call Activities 子流程
子流程是嵌入式的,不可重用,必須從None Start Event開始
而Call Activities本身呼叫的就是完整的流程
7. Process Initiation Authorization 初始化認證
<process id="potentialStarter"> <extensionElements> <activiti:potentialStarter> <resourceAssignmentExpression> <formalExpression>group2, group(group3), user(user3)</formalExpression> </resourceAssignmentExpression> </activiti:potentialStarter> </extensionElements> <startEvent id="theStart"/> ... 或 <process id="potentialStarter" activiti:candidateStarterUsers="user1, user2" activiti:candidateStarterGroups="group1"> ...
8. Data objects 流程資料
<process id="dataObjectScope" name="Data Object Scope" isExecutable="true"> <dataObject id="dObj123" name="StringTest123" itemSubjectRef="xsd:string"> <extensionElements> <activiti:value>Testing123</activiti:value> </extensionElements> </dataObject> ...
9. Form properties 表單屬性
- StartFormData FormService.getStartFormData(String processDefinitionId)
- TaskFormdata FormService.getTaskFormData(String taskId)
<userTask id="task"> <extensionElements> <activiti:formProperty id="room" /> <activiti:formProperty id="duration" type="long"/> <activiti:formProperty id="speaker" variable="SpeakerName" writable="false" /> <activiti:formProperty id="street" expression="#{address.street}" required="true" /> </extensionElements> </userTask>
表單屬性 room 對應--〉 流程變數 room 型別: String
表單屬性 duration 對應--〉 流程變數 duration 型別: java.lang.Long
表單屬性 speaker 對應--〉 流程變數 SpeakerName. 它是TaskFormData物件.
表單屬性 street 對應--〉 Java bean 屬性 street定義在流程變數 address
<startEvent id="start"> <extensionElements> <activiti:formProperty id="speaker" name="Speaker" variable="SpeakerName" type="string" /> <activiti:formProperty id="start" type="date" datePattern="dd-MMM-yyyy" /> <activiti:formProperty id="direction" type="enum"> <activiti:value id="left" name="Go Left" /> <activiti:value id="right" name="Go Right" /> <activiti:value id="up" name="Go Up" /> <activiti:value id="down" name="Go Down" /> </activiti:formProperty> </extensionElements> </startEvent>
<startEvent> <extensionElements> <activiti:formProperty id="numberOfDays" name="Number of days" value="${numberOfDays}" type="long" required="true"/> <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" /> <activiti:formProperty id="vacationMotivation" name="Motivation" value="${vacationMotivation}" type="string" /> </extensionElements> </userTask>
表單:
10. External form rendering 外部表單整合
提交表單屬性,第三方表單系統傳送資料:
ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)
FormService.submitTaskFormData(String taskId, Map<String,String> properties)
獲得屬性,Acticiti接收處理資料:
StartFormData FormService.getStartFormData(String processDefinitionId)
TaskFormdata FormService.getTaskFormData(String taskId)
.