1. 程式人生 > 實用技巧 >Liferay7 BPM門戶開發之2: BPMN 2.0 規範入門 (Activiti BPMN extensions)

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>

  <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.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).