1. <dd id="erndk"></dd>
                1. 使用bpmn-js實現activiti的流程設計器

                  易樣 2020/3/20 11:01:45


                  寫在前面:博主實在抽不出時間準備demo,因為大家有需求,先把文章發給大家看

                  Hello 大家好,我是易樣(容易不一樣,我們不一樣,一天一個樣)。

                  好久沒更新文章了,沒更新文章的這些時間我都在閉關修煉,努力提升自身技術,畢竟我2020年的flag是成為大牛。

                  今天給大家帶來的這篇文章是整理我使用bpmn-js實現activiti流程設計器的經驗之談,bpmn-js的中文文檔不多,很多人都不如何入手開發,并且bpmn-js的后端使用的是Camunda,如何使用activiti也是困擾了很多開發。

                  不要怕,讓小姐姐來教教你。

                  問題

                  需要前后端分離、覺得activiti的設計器不好用、使用bpmn-js實現設計器,但后端用的是activiti,xml不兼容怎么辦?

                  解決

                  不止我這一種解決方法,我這里只提供我的解決方法。

                  1 使用Bpmn-js開發設計器

                  關于bpmn-js如何使用建議搭建去github上面搜索,這里貼上官網地址: github.com/bpmn-io/bpm…

                  官網案例地址:github.com/bpmn-io/bpm…

                  筆者開發設計器時參考了霖呆呆的關于bpmn-js從0開發的一系列文章,地址: juejin.im/post/5def37…

                  相信大家看完以上我貼的文章,對bpmn-js已經很熟悉了;接下來我來解釋一下我的項目:

                  • 環境:windows10
                  • 開發工具:vscode、IDEA
                  • 技術:vue、springboot

                  1.1 自定義右邊屬性面板

                  如圖,是我完全自定義的屬性面板

                  部分代碼如下:

                  <template>
                    <div>
                      <el-container style="height: 700px">
                        <el-aside width="80%" style="border: 1px solid #DCDFE6" >
                          <div ref="canvas" style="width: 100%;height: 100%"></div>
                        </el-aside>      
                  <el-main style="border: 1px solid #DCDFE6;background-color:#FAFAFA      ">
                            <el-form label-width="auto" size="mini" label-position="top">
                              <!-- 動態顯示屬性面板 -->            
                  <component :is= "propsComponent" :element= "element" :key= "key"></component>
                            </el-form>
                        </el-main>
                      </el-container>
                    </div>
                  </template>復制代碼

                  我是通過propsComponent屬性的變化來顯示不同事件的屬性,比如用戶任務的屬性、網關的屬性

                  propsComponent屬性是通過監聽modeler、element來改變值的,代碼如下:

                  addModelerListener() {
                          // 監聽 modeler
                          const bpmnjs = this.bpmnModeler
                          const that = this
                          // 'shape.removed', 'connect.end', 'connect.move'
                          const events = ['shape.added', 'shape.move.end', 'shape.removed']
                          events.forEach(function(event) {
                            that.bpmnModeler.on(event, e => {
                              var elementRegistry = bpmnjs.get('elementRegistry')
                              var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
                              // console.log(shape)
                              if (event === 'shape.added') {
                                console.log('新增了shape');
                                // 展示新增圖形的屬性
                                that.key = e.element.id.replace('_label', '');
                                that.propsComponent = bpmnHelper.getComponentByEleType(shape.type);
                                that.element = e.element;
                                
                              } else if (event === 'shape.move.end') {
                                console.log('移動了shape')
                                // 展示新增圖形的屬性
                                that.key = shape.id;
                                that.propsComponent = bpmnHelper.getComponentByEleType(shape.type);
                                that.element = e.shape;
                              } else if (event === 'shape.removed') {
                                console.log('刪除了shape')
                                // 展示默認的屬性
                                that.propsComponent = 'CommonProps'
                              }
                            })
                          })
                        },
                        addEventBusListener() {
                          // 監聽 element
                          let that = this
                          const eventBus = this.bpmnModeler.get('eventBus')
                          const eventTypes = ['element.click', 'element.changed', 'selection.changed']
                          eventTypes.forEach(function(eventType) {
                            eventBus.on(eventType, function(e) {
                              if (eventType === 'element.changed') {
                                that.elementChanged(e)
                              } else if (eventType === 'element.click') {
                                console.log('點擊了element');
                                if (!e || e.element.type == 'bpmn:Process') {
                                  that.key = '1';
                                  that.propsComponent = 'CommonProps'
                                  that.element = e.element;
                                } else {
                                  // 展示新增圖形的屬性
                                  that.key = e.element.id;
                                  that.propsComponent = bpmnHelper.getComponentByEleType(e.element.type);
                                  that.element = e.element;
                                }
                                
                              }
                            })
                          })
                        },復制代碼

                  由于vue的特殊性,在使用屬性組件前,還需要引入組件

                  components: {
                      CommonProps,
                      ProcessProps,
                      StartEventProps,
                      EndEventProps,
                      IntermediateThrowEventProps,
                      ExclusiveGatewayProps,
                      ParallelGatewayProps,
                      InclusiveGatewayProps,
                      UserTaskProps,
                      SequenceFlowProps,
                      CallActivityProps
                    },復制代碼

                  接下來就是實現各個事件屬性的頁面了。

                  完整代碼見github:因為大家急需文章就先發文章了,加上博主忙,demo還沒時間寫

                  我特意為你們單獨抽離的demo,不要辜負我的良苦用心呀

                  1.2 適配activiti

                  由于bpmn-js官方是適配camunda的,所以對activiti存在不兼容的地方,為了讓bpmn-js能使用activiti,我們需要在BpmnModeler中擴展activiti 代碼如下:

                  import activitiModdleDescriptor from '../js/activiti.json';復制代碼

                  this.bpmnModeler = new BpmnModeler({
                            container: canvas,
                            //添加屬性面板,添加翻譯模塊
                            additionalModules: [
                                customTranslateModule,
                                customControlsModule  
                            ],
                            //模塊拓展,拓展activiti的描述
                            moddleExtensions: {
                                activiti: activitiModdleDescriptor
                            }
                          });復制代碼

                  關于activiti.json文件,我建議你看自定義元模型示例

                  部分內容如下:

                  {
                    "name": "Activiti",
                    "uri": "http://activiti.org/bpmn",
                    "prefix": "activiti",
                    "xml": {
                      "tagAlias": "lowerCase"
                    },
                    "associations": [],
                    "types": [
                      {
                        "name": "Definitions",
                        "isAbstract": true,
                        "extends": [
                          "bpmn:Definitions"
                        ],
                        "properties": [
                          {
                            "name": "diagramRelationId",
                            "isAttr": true,
                            "type": "String"
                          }
                        ]
                      }
                    ],
                    "emumerations": [ ]
                  }復制代碼

                  注意:uri、prefix

                  1.3 xml

                  我項目設計器設計的流程xml如下:

                  <?xml version="1.0" encoding="UTF-8"?>
                  <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://activiti.org/bpmn">
                    <process id="T_00129645774714699809" name="test" isExecutable="true">
                      <startEvent id="StartEvent_01elwlp" name="交易開始" />
                      <userTask id="UserTask_1i458h3" name="申請" activiti:formKey="T_00129645931707498529" />
                      <sequenceFlow id="SequenceFlow_0jb1zmg" sourceRef="StartEvent_01elwlp" targetRef="UserTask_1i458h3" />
                      <userTask id="UserTask_1vytaw5" name="審批" activiti:formKey="T_00129646086359875617" activiti:candidateGroups="25e1ff13-494b-4a93-8808-084115398ee7" activiti:multiinstance_condition="97" multiinstance_type="Parallel" nodetype="非關鍵節點" txtype="審批" />
                      <sequenceFlow id="SequenceFlow_13fiwzg" sourceRef="UserTask_1i458h3" targetRef="UserTask_1vytaw5" />
                      <exclusiveGateway id="ExclusiveGateway_1bwl3n5" />
                      <sequenceFlow id="SequenceFlow_0pp91mp" sourceRef="UserTask_1vytaw5" targetRef="ExclusiveGateway_1bwl3n5" />
                      <userTask id="UserTask_0grjesn" name="記賬" activiti:formKey="T_00129646230853648417" />
                      <sequenceFlow id="SequenceFlow_0jehcxl" sourceRef="ExclusiveGateway_1bwl3n5" targetRef="UserTask_0grjesn" />
                      <endEvent id="EndEvent_0oqdz1f" name="交易完成" />
                      <sequenceFlow id="SequenceFlow_1apiady" sourceRef="UserTask_0grjesn" targetRef="EndEvent_0oqdz1f" />
                    </process>
                    <bpmndi:BPMNDiagram id="BPMNDiagram_T_00129645774714699809">
                      <bpmndi:BPMNPlane id="BPMNPlane_T_00129645774714699809" bpmnElement="T_00129645774714699809">
                        <bpmndi:BPMNShape id="BPMNShape_StartEvent_01elwlp" bpmnElement="StartEvent_01elwlp">
                          <omgdc:Bounds x="202" y="152" width="36" height="36" />
                        </bpmndi:BPMNShape>
                        <bpmndi:BPMNShape id="BPMNShape_UserTask_1i458h3" bpmnElement="UserTask_1i458h3">
                          <omgdc:Bounds x="290" y="130" width="100" height="80" />
                        </bpmndi:BPMNShape>
                        <bpmndi:BPMNShape id="BPMNShape_UserTask_1vytaw5" bpmnElement="UserTask_1vytaw5">
                          <omgdc:Bounds x="450" y="130" width="100" height="80" />
                        </bpmndi:BPMNShape>
                        <bpmndi:BPMNShape id="BPMNShape_ExclusiveGateway_1bwl3n5" bpmnElement="ExclusiveGateway_1bwl3n5" isMarkerVisible="true">
                          <omgdc:Bounds x="615" y="145" width="50" height="50" />
                        </bpmndi:BPMNShape>
                        <bpmndi:BPMNShape id="BPMNShape_UserTask_0grjesn" bpmnElement="UserTask_0grjesn">
                          <omgdc:Bounds x="730" y="130" width="100" height="80" />
                        </bpmndi:BPMNShape>
                        <bpmndi:BPMNShape id="BPMNShape_EndEvent_0oqdz1f" bpmnElement="EndEvent_0oqdz1f">
                          <omgdc:Bounds x="902" y="152" width="36" height="36" />
                        </bpmndi:BPMNShape>
                        <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1apiady" bpmnElement="SequenceFlow_1apiady">
                          <omgdi:waypoint x="830" y="170" />
                          <omgdi:waypoint x="902" y="170" />
                        </bpmndi:BPMNEdge>
                        <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_0jb1zmg" bpmnElement="SequenceFlow_0jb1zmg">
                          <omgdi:waypoint x="238" y="170" />
                          <omgdi:waypoint x="290" y="170" />
                        </bpmndi:BPMNEdge>
                        <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_0pp91mp" bpmnElement="SequenceFlow_0pp91mp">
                          <omgdi:waypoint x="550" y="170" />
                          <omgdi:waypoint x="615" y="170" />
                        </bpmndi:BPMNEdge>
                        <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_13fiwzg" bpmnElement="SequenceFlow_13fiwzg">
                          <omgdi:waypoint x="390" y="170" />
                          <omgdi:waypoint x="450" y="170" />
                        </bpmndi:BPMNEdge>
                        <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_0jehcxl" bpmnElement="SequenceFlow_0jehcxl">
                          <omgdi:waypoint x="665" y="170" />
                          <omgdi:waypoint x="730" y="170" />
                        </bpmndi:BPMNEdge>
                      </bpmndi:BPMNPlane>
                    </bpmndi:BPMNDiagram>
                  </definitions>復制代碼

                  節點里的屬性大部分都是我自定義的屬性

                  2 后端activiti實現

                  具體怎么搭建activiti環境,相信大家都能百度到,我只介紹怎么將bpmn-js和activiti兼容

                  3.1 解析BPMN文件

                  如圖,展示了一個XML格式的流程文件如何經過幾個大的步驟部署到引擎的過程

                  3.2 先由前端傳xml保存到后端開始

                  http請求將攜帶主要的兩個參數,bpmn_xml和svg_xml

                  由于activiti保存在數據庫中的是json文件,所以我們需要將bpmn_xml文件轉換成json

                  activiti官方提供的轉換方法并不能滿足我,我自定義了轉換方法和解析器,activiti官方也允許你自定義解析器

                  先上方法:

                  public static JsonNode converterXmlToJson(String bpmnXml) {
                          // 創建轉換對象
                          BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
                          // XMLStreamReader讀取XML資源
                          XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
                          StringReader stringReader = new StringReader(bpmnXml);
                          XMLStreamReader xmlStreamReader = null;
                          try {
                              xmlStreamReader = xmlInputFactory.createXMLStreamReader(stringReader);
                          } catch (XMLStreamException e) {
                              e.printStackTrace();
                          }
                          // UserTaskXMLConverter類是我自定義的
                          BpmnXMLConverter.addConverter(new UserTaskXMLConverter());
                          // 把xml轉換成BpmnModel對象
                          BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xmlStreamReader);
                          // BpmnJsonConverter類是我自定義的
                          // 創建轉換對象
                          BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
                          // 把BpmnModel對象轉換成json
                          JsonNode jsonNodes = bpmnJsonConverter.convertToJson(bpmnModel);
                          // 返回的json會被保存到數據庫中
                          return jsonNodes;
                      }復制代碼

                  以上代碼使用了Activiti的activiti-bpmn-converter模塊提供的BpmnModel對象與XML的互轉功能,通過創建org.activiti.bpmn.converter.BpmnXMLConverter類對象調用相應的方法即可實現BpmnModel對象與XML之間的轉換操作。

                  首先,自定義類UserTaskXMLConverter是因為我的用戶任務事件中有自定義的屬性;在將xml轉為BpmnModel時,如果是用戶任務事件就會走我自定義的UserTaskXMLConverter類

                  相關代碼見github:因為大家急需文章就先發文章了,加上博主忙,demo還沒時間寫

                  然后是將BpmnModel轉為json,注意每個bpmnModel.attributes下存方著所有屬性

                  3.3 自定義的BpmnJsonConverter文件

                  Activiti提供的activiti-json-converter模塊中提供了BpmnJsonConverter類,我們對比一下我自定義的和官方的

                  發現,我們自定義的類中的static中有幾個Custom開頭的類,見名知義,這些類是關于用戶任務、流程、網關的轉換類。

                  問:為何要自定義這些類呢?

                  答:

                  1. 因為前端自定義屬性(例如:多實例屬性、默認流程屬性)使用官方的toBpmnModel轉換是會丟失自定義屬性的,我們自定義類主要是將自定義屬性放在attribute中,并且轉換多實例屬性為Activiti的BPMN規范接受。
                  2. convertElementToJson時加上自定義的屬性鍵值復制代碼

                  用戶任務自定義屬性轉換相關代碼:

                  // 多實例類型
                  String multiInstanceType = getPropertyValueAsString(PROPERTY_MULTIINSTANCE_TYPE, elementNode);
                  // 通過權重
                  String multiInstanceCondition = getPropertyValueAsString(PROPERTY_MULTIINSTANCE_CONDITION, elementNode);
                  if (StringUtils.isNotEmpty(multiInstanceType) && !"none".equalsIgnoreCase(multiInstanceType)) {
                      String name = getPropertyValueAsString(PROPERTY_NAME, elementNode);
                      MultiInstanceLoopCharacteristics multiInstanceObject = new MultiInstanceLoopCharacteristics();
                      if ("sequential".equalsIgnoreCase(multiInstanceType))     {
                          multiInstanceObject.setSequential(true);
                      } else {
                          multiInstanceObject.setSequential(false);
                      }
                      if (StringUtils.isNotEmpty(multiInstanceCondition)) {
                          try {
                              Integer.valueOf(multiInstanceCondition);
                          } catch (Exception ex) {
                              throw new WorkflowApiException(name + "配置成了會簽,但通過權重不是一個整數");
                          }
                          multiInstanceObject.setCompletionCondition("${nextTaskEvaluator.isComplete(execution," + multiInstanceCondition + ")}");
                      } else {
                          throw new WorkflowApiException(name + "配置成了會簽,但沒有配置通過權重");
                      }
                  }復制代碼

                  3.4 Bpmn解析處理器

                  Activiti支持在解析BPMN資源文件時允許自定義BPMN解析處理器(BpmnParseHandler)參與,可以在開始解析一個元素(Element)或解析完之后調用自定義的BPMN解析處理器,在自定義的解析處理器中,我們可以更改一些BPMN對象的屬性。

                  添加BPMN解析處理器可以在Activiti引擎配置文件中配置屬性“preBpmnParseHandlers”和“postBpmnParseHandlers”。下面的代碼針對Pre(前置)和Post(后置)類型分別添加了一個解析處理器

                  上面的代碼添加了兩種類型的BPMN解析處理器,之所以區分類型是為了更細致地劃分處理器類型;Pre類型處理器是總是排在第一位執行,也就是在所有流程文件中定義地元素之前,而Post類型的處理器被放在最后執行,也就是所有流程文件中定義的而元素之后。如果解析處理器有特定的順序要求,就可以用Pre和Post類型來區分。

                  小結

                  總體來說,完整開發下來還是比較費力,需要你對bpmn-js以及activiti有一定的了解并且有一定的耐心。

                  bpmn-js和activiti也是我慢慢啃下來的,如果感覺文章對你有幫助點關注、點贊、贊賞、關注公眾號都不嫌棄。

                  啦啦啦~~ ,寫完了寫完了,我又是一個開心的小仙女了。



                  附件:

                  UserTaskXMLConverter.java

                  import org.activiti.bpmn.converter.BaseBpmnXMLConverter;
                  import org.activiti.bpmn.converter.XMLStreamReaderUtil;
                  import org.activiti.bpmn.converter.child.BaseChildElementParser;
                  import org.activiti.bpmn.converter.util.BpmnXMLUtil;
                  import org.activiti.bpmn.converter.util.CommaSplitter;
                  import org.activiti.bpmn.model.BaseElement;
                  import org.activiti.bpmn.model.BpmnModel;
                  import org.activiti.bpmn.model.CustomProperty;
                  import org.activiti.bpmn.model.ExtensionAttribute;
                  import org.activiti.bpmn.model.Resource;
                  import org.activiti.bpmn.model.UserTask;
                  import org.activiti.bpmn.model.alfresco.AlfrescoUserTask;
                  import org.apache.commons.lang3.StringUtils;
                  
                  import javax.xml.stream.XMLStreamReader;
                  import javax.xml.stream.XMLStreamWriter;
                  import java.util.ArrayList;
                  import java.util.Arrays;
                  import java.util.HashMap;
                  import java.util.HashSet;
                  import java.util.List;
                  import java.util.Map;
                  import java.util.Set;
                  
                  public class UserTaskXMLConverter extends BaseBpmnXMLConverter {
                      
                      /**
                       * default attributes taken from bpmn spec and from activiti extension
                       */
                      protected static final List<ExtensionAttribute> defaultUserTaskAttributes = Arrays
                              .asList(new ExtensionAttribute(null, ATTRIBUTE_FORM_FORMKEY),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_DUEDATE),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_ASSIGNEE),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_PRIORITY),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_CANDIDATEUSERS),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_CANDIDATEGROUPS),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_CATEGORY),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_SERVICE_EXTENSIONID),
                                      new ExtensionAttribute(null, ATTRIBUTE_TASK_USER_SKIP_EXPRESSION));
                      protected Map<String, BaseChildElementParser> childParserMap = new HashMap<String, BaseChildElementParser>();
                      
                      public UserTaskXMLConverter() {
                          HumanPerformerParser humanPerformerParser = new HumanPerformerParser();
                          childParserMap.put(humanPerformerParser.getElementName(),
                                  humanPerformerParser);
                          PotentialOwnerParser potentialOwnerParser = new PotentialOwnerParser();
                          childParserMap.put(potentialOwnerParser.getElementName(),
                                  potentialOwnerParser);
                          CustomIdentityLinkParser customIdentityLinkParser = new CustomIdentityLinkParser();
                          childParserMap.put(customIdentityLinkParser.getElementName(),
                                  customIdentityLinkParser);
                      }
                      
                      public Class<? extends BaseElement> getBpmnElementType() {
                          return UserTask.class;
                      }
                      
                      @Override
                      protected String getXMLElementName() {
                          return ELEMENT_TASK_USER;
                      }
                      
                      @Override
                      @SuppressWarnings("unchecked")
                      protected BaseElement convertXMLToElement(XMLStreamReader xtr,
                                                                BpmnModel model) throws Exception {
                          String formKey = xtr.getAttributeValue(null, ATTRIBUTE_FORM_FORMKEY);
                          UserTask userTask = null;
                          if (StringUtils.isNotEmpty(formKey)) {
                              if (model.getUserTaskFormTypes() != null
                                      && model.getUserTaskFormTypes().contains(formKey)) {
                                  userTask = new AlfrescoUserTask();
                              }
                          }
                          if (userTask == null) {
                              userTask = new UserTask();
                          }
                          BpmnXMLUtil.addXMLLocation(userTask, xtr);
                          userTask
                                  .setDueDate(xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_DUEDATE));
                          userTask
                                  .setCategory(xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_CATEGORY));
                          userTask.setFormKey(formKey);
                          userTask
                                  .setAssignee(xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_ASSIGNEE));
                          userTask.setOwner(xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_OWNER));
                          userTask
                                  .setPriority(xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_PRIORITY));
                          
                          if (StringUtils.isNotEmpty(
                                  xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_CANDIDATEUSERS))) {
                              String expression = xtr.getAttributeValue(null,
                                      ATTRIBUTE_TASK_USER_CANDIDATEUSERS);
                              userTask.getCandidateUsers().addAll(parseDelimitedList(expression));
                          }
                          
                          if (StringUtils.isNotEmpty(
                                  xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_CANDIDATEGROUPS))) {
                              String expression = xtr.getAttributeValue(null,
                                      ATTRIBUTE_TASK_USER_CANDIDATEGROUPS);
                              userTask.getCandidateGroups().addAll(parseDelimitedList(expression));
                          }
                          
                          userTask.setExtensionId(
                                  xtr.getAttributeValue(null, ATTRIBUTE_TASK_SERVICE_EXTENSIONID));
                          
                          if (StringUtils.isNotEmpty(
                                  xtr.getAttributeValue(null, ATTRIBUTE_TASK_USER_SKIP_EXPRESSION))) {
                              String expression = xtr.getAttributeValue(null,
                                      ATTRIBUTE_TASK_USER_SKIP_EXPRESSION);
                              userTask.setSkipExpression(expression);
                          }
                          // 全部的屬性都在這里
                          BpmnXMLUtil.addCustomAttributes(xtr, userTask, defaultElementAttributes,
                                  defaultActivityAttributes, defaultUserTaskAttributes);
                          
                          parseChildElements(getXMLElementName(), userTask, childParserMap, model,
                                  xtr);
                          
                          return userTask;
                      }
                      
                      @Override
                      @SuppressWarnings("unchecked")
                      protected void writeAdditionalAttributes(BaseElement element, BpmnModel model,
                                                               XMLStreamWriter xtw) throws Exception {
                          UserTask userTask = (UserTask) element;
                          writeQualifiedAttribute(ATTRIBUTE_TASK_USER_ASSIGNEE,
                                  userTask.getAssignee(), xtw);
                          writeQualifiedAttribute(ATTRIBUTE_TASK_USER_OWNER, userTask.getOwner(),
                                  xtw);
                          writeQualifiedAttribute(ATTRIBUTE_TASK_USER_CANDIDATEUSERS,
                                  convertToDelimitedString(userTask.getCandidateUsers()), xtw);
                          writeQualifiedAttribute(ATTRIBUTE_TASK_USER_CANDIDATEGROUPS,
                                  convertToDelimitedString(userTask.getCandidateGroups()), xtw);
                          writeQualifiedAttribute(ATTRIBUTE_TASK_USER_DUEDATE, userTask.getDueDate(),
                                  xtw);
                          writeQualifiedAttribute(ATTRIBUTE_TASK_USER_CATEGORY,
                                  userTask.getCategory(), xtw);
                          writeQualifiedAttribute(ATTRIBUTE_FORM_FORMKEY, userTask.getFormKey(), xtw);
                          if (userTask.getPriority() != null) {
                              writeQualifiedAttribute(ATTRIBUTE_TASK_USER_PRIORITY,
                                      userTask.getPriority().toString(), xtw);
                          }
                          if (StringUtils.isNotEmpty(userTask.getExtensionId())) {
                              writeQualifiedAttribute(ATTRIBUTE_TASK_SERVICE_EXTENSIONID,
                                      userTask.getExtensionId(), xtw);
                          }
                          if (userTask.getSkipExpression() != null) {
                              writeQualifiedAttribute(ATTRIBUTE_TASK_USER_SKIP_EXPRESSION,
                                      userTask.getSkipExpression(), xtw);
                          }
                          // write custom attributes
                          BpmnXMLUtil.writeCustomAttributes(userTask.getAttributes().values(), xtw,
                                  defaultElementAttributes, defaultActivityAttributes,
                                  defaultUserTaskAttributes);
                      }
                      
                      @Override
                      protected boolean writeExtensionChildElements(BaseElement element,
                                                                    boolean didWriteExtensionStartElement, XMLStreamWriter xtw)
                              throws Exception {
                          UserTask userTask = (UserTask) element;
                          didWriteExtensionStartElement = writeFormProperties(userTask,
                                  didWriteExtensionStartElement, xtw);
                          didWriteExtensionStartElement = writeCustomIdentities(element,
                                  didWriteExtensionStartElement, xtw);
                          if (!userTask.getCustomProperties().isEmpty()) {
                              for (CustomProperty customProperty : userTask.getCustomProperties()) {
                                  
                                  if (StringUtils.isEmpty(customProperty.getSimpleValue())) {
                                      continue;
                                  }
                                  
                                  if (!didWriteExtensionStartElement) {
                                      xtw.writeStartElement(ELEMENT_EXTENSIONS);
                                      didWriteExtensionStartElement = true;
                                  }
                                  xtw.writeStartElement(ACTIVITI_EXTENSIONS_PREFIX,
                                          customProperty.getName(), ACTIVITI_EXTENSIONS_NAMESPACE);
                                  xtw.writeCharacters(customProperty.getSimpleValue());
                                  xtw.writeEndElement();
                              }
                          }
                          return didWriteExtensionStartElement;
                      }
                      
                      protected boolean writeCustomIdentities(BaseElement element,
                                                              boolean didWriteExtensionStartElement, XMLStreamWriter xtw)
                              throws Exception {
                          UserTask userTask = (UserTask) element;
                          if (userTask.getCustomUserIdentityLinks().isEmpty()
                                  && userTask.getCustomGroupIdentityLinks().isEmpty()) {
                              return didWriteExtensionStartElement;
                          }
                          
                          if (!didWriteExtensionStartElement) {
                              xtw.writeStartElement(ELEMENT_EXTENSIONS);
                              didWriteExtensionStartElement = true;
                          }
                          Set<String> identityLinkTypes = new HashSet<String>();
                          identityLinkTypes.addAll(userTask.getCustomUserIdentityLinks().keySet());
                          identityLinkTypes.addAll(userTask.getCustomGroupIdentityLinks().keySet());
                          for (String identityType : identityLinkTypes) {
                              writeCustomIdentities(userTask, identityType,
                                      userTask.getCustomUserIdentityLinks().get(identityType),
                                      userTask.getCustomGroupIdentityLinks().get(identityType), xtw);
                          }
                          
                          return didWriteExtensionStartElement;
                      }
                      
                      protected void writeCustomIdentities(UserTask userTask, String identityType,
                                                           Set<String> users, Set<String> groups, XMLStreamWriter xtw)
                              throws Exception {
                          xtw.writeStartElement(ACTIVITI_EXTENSIONS_PREFIX, ELEMENT_CUSTOM_RESOURCE,
                                  ACTIVITI_EXTENSIONS_NAMESPACE);
                          writeDefaultAttribute(ATTRIBUTE_NAME, identityType, xtw);
                          
                          List<String> identityList = new ArrayList<String>();
                          
                          if (users != null) {
                              for (String userId : users) {
                                  identityList.add("user(" + userId + ")");
                              }
                          }
                          
                          if (groups != null) {
                              for (String groupId : groups) {
                                  identityList.add("group(" + groupId + ")");
                              }
                          }
                          
                          String delimitedString = convertToDelimitedString(identityList);
                          
                          xtw.writeStartElement(ELEMENT_RESOURCE_ASSIGNMENT);
                          xtw.writeStartElement(ELEMENT_FORMAL_EXPRESSION);
                          xtw.writeCharacters(delimitedString);
                          xtw.writeEndElement(); // End ELEMENT_FORMAL_EXPRESSION
                          xtw.writeEndElement(); // End ELEMENT_RESOURCE_ASSIGNMENT
                          
                          xtw.writeEndElement(); // End ELEMENT_CUSTOM_RESOURCE
                      }
                      
                      @Override
                      protected void writeAdditionalChildElements(BaseElement element,
                                                                  BpmnModel model, XMLStreamWriter xtw) throws Exception {
                      }
                      
                      public class HumanPerformerParser extends BaseChildElementParser {
                          
                          public String getElementName() {
                              return "humanPerformer";
                          }
                          
                          public void parseChildElement(XMLStreamReader xtr,
                                                        BaseElement parentElement, BpmnModel model) throws Exception {
                              String resourceElement = XMLStreamReaderUtil.moveDown(xtr);
                              if (StringUtils.isNotEmpty(resourceElement)
                                      && ELEMENT_RESOURCE_ASSIGNMENT.equals(resourceElement)) {
                                  String expression = XMLStreamReaderUtil.moveDown(xtr);
                                  if (StringUtils.isNotEmpty(expression)
                                          && ELEMENT_FORMAL_EXPRESSION.equals(expression)) {
                                      ((UserTask) parentElement).setAssignee(xtr.getElementText());
                                  }
                              }
                          }
                      }
                      
                      public class PotentialOwnerParser extends BaseChildElementParser {
                          
                          public String getElementName() {
                              return "potentialOwner";
                          }
                          
                          public void parseChildElement(XMLStreamReader xtr,
                                                        BaseElement parentElement, BpmnModel model) throws Exception {
                              String resourceElement = XMLStreamReaderUtil.moveDown(xtr);
                              if (StringUtils.isNotEmpty(resourceElement)
                                      && ELEMENT_RESOURCE_ASSIGNMENT.equals(resourceElement)) {
                                  String expression = XMLStreamReaderUtil.moveDown(xtr);
                                  if (StringUtils.isNotEmpty(expression)
                                          && ELEMENT_FORMAL_EXPRESSION.equals(expression)) {
                                      
                                      List<String> assignmentList = CommaSplitter
                                              .splitCommas(xtr.getElementText());
                                      
                                      for (String assignmentValue : assignmentList) {
                                          if (assignmentValue == null) {
                                              continue;
                                          }
                                          
                                          assignmentValue = assignmentValue.trim();
                                          
                                          if (assignmentValue.length() == 0) {
                                              continue;
                                          }
                                          
                                          String userPrefix = "user(";
                                          String groupPrefix = "group(";
                                          if (assignmentValue.startsWith(userPrefix)) {
                                              assignmentValue = assignmentValue
                                                      .substring(userPrefix.length(), assignmentValue.length() - 1)
                                                      .trim();
                                              ((UserTask) parentElement).getCandidateUsers()
                                                      .add(assignmentValue);
                                          } else if (assignmentValue.startsWith(groupPrefix)) {
                                              assignmentValue = assignmentValue
                                                      .substring(groupPrefix.length(), assignmentValue.length() - 1)
                                                      .trim();
                                              ((UserTask) parentElement).getCandidateGroups()
                                                      .add(assignmentValue);
                                          } else {
                                              ((UserTask) parentElement).getCandidateGroups()
                                                      .add(assignmentValue);
                                          }
                                      }
                                  }
                              } else if (StringUtils.isNotEmpty(resourceElement)
                                      && ELEMENT_RESOURCE_REF.equals(resourceElement)) {
                                  String resourceId = xtr.getElementText();
                                  if (model.containsResourceId(resourceId)) {
                                      Resource resource = model.getResource(resourceId);
                                      ((UserTask) parentElement).getCandidateGroups()
                                              .add(resource.getName());
                                  } else {
                                      Resource resource = new Resource(resourceId, resourceId);
                                      model.addResource(resource);
                                      ((UserTask) parentElement).getCandidateGroups()
                                              .add(resource.getName());
                                  }
                              }
                          }
                      }
                      
                      public class CustomIdentityLinkParser extends BaseChildElementParser {
                          
                          public String getElementName() {
                              return ELEMENT_CUSTOM_RESOURCE;
                          }
                          
                          public void parseChildElement(XMLStreamReader xtr,
                                                        BaseElement parentElement, BpmnModel model) throws Exception {
                              String identityLinkType = xtr
                                      .getAttributeValue(ACTIVITI_EXTENSIONS_NAMESPACE, ATTRIBUTE_NAME);
                              
                              // the attribute value may be unqualified
                              if (identityLinkType == null) {
                                  identityLinkType = xtr.getAttributeValue(null, ATTRIBUTE_NAME);
                              }
                              
                              if (identityLinkType == null) {
                                  return;
                              }
                              
                              String resourceElement = XMLStreamReaderUtil.moveDown(xtr);
                              if (StringUtils.isNotEmpty(resourceElement)
                                      && ELEMENT_RESOURCE_ASSIGNMENT.equals(resourceElement)) {
                                  String expression = XMLStreamReaderUtil.moveDown(xtr);
                                  if (StringUtils.isNotEmpty(expression)
                                          && ELEMENT_FORMAL_EXPRESSION.equals(expression)) {
                                      
                                      List<String> assignmentList = CommaSplitter
                                              .splitCommas(xtr.getElementText());
                                      
                                      for (String assignmentValue : assignmentList) {
                                          if (assignmentValue == null) {
                                              continue;
                                          }
                                          
                                          assignmentValue = assignmentValue.trim();
                                          
                                          if (assignmentValue.length() == 0) {
                                              continue;
                                          }
                                          
                                          String userPrefix = "user(";
                                          String groupPrefix = "group(";
                                          if (assignmentValue.startsWith(userPrefix)) {
                                              assignmentValue = assignmentValue
                                                      .substring(userPrefix.length(), assignmentValue.length() - 1)
                                                      .trim();
                                              ((UserTask) parentElement)
                                                      .addCustomUserIdentityLink(assignmentValue, identityLinkType);
                                          } else if (assignmentValue.startsWith(groupPrefix)) {
                                              assignmentValue = assignmentValue
                                                      .substring(groupPrefix.length(), assignmentValue.length() - 1)
                                                      .trim();
                                              ((UserTask) parentElement).addCustomGroupIdentityLink(
                                                      assignmentValue, identityLinkType);
                                          } else {
                                              ((UserTask) parentElement).addCustomGroupIdentityLink(
                                                      assignmentValue, identityLinkType);
                                          }
                                      }
                                  }
                              }
                          }
                      }
                  }
                  復制代碼


                  隨時隨地學軟件編程-關注百度小程序和微信小程序
                  關于找一找教程網

                  本站文章僅代表作者觀點,不代表本站立場,所有文章非營利性免費分享。
                  本站提供了軟件編程、網站開發技術、服務器運維、人工智能等等IT技術文章,希望廣大程序員努力學習,讓我們用科技改變世界。
                  [使用bpmn-js實現activiti的流程設計器]http://www.yachtsalesaustralia.com/tech/detail-120072.html

                  贊(0)
                  關注微信小程序
                  程序員編程王-隨時隨地學編程

                  掃描二維碼或查找【程序員編程王】

                  可以隨時隨地學編程啦!

                  技術文章導航 更多>
                  国产在线拍揄自揄视频菠萝

                        1. <dd id="erndk"></dd>