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>
<timeCycle>R3/PT10H/${EndDate}</timeCycle>
</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.7 Boundary 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.1 Conditional sequence flow
帶有UEL條件表達式的順序流
<sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${order.price > 100 && order.price < 250}]]> </conditionExpression> </sequenceFlow>
2.2 Default 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.2 Parallel 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.1 User 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.2 Script Task
略
4.3 Java Service Task
4.4 Business 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.5 Camel 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)
.