使用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
- 2022-05-20vue2.x版本中computed和watch的使用入門詳解-watch篇
- 2022-05-20Windows服務器下使用jenkis部署VUE項目
- 2022-05-20Element UI在線運行錯誤, (Uncaught ReferenceError: Vue is not defined)
- 2022-05-20vue 的toast組件
- 2022-05-19Jenkins自動構建部署vue項目到遠程Windows服務器上 騰訊工蜂(git) 一鍵打包+部署+解壓+發布(下)---jenkins的環境配置和使用
- 2022-05-19vue實現lodop打印功能--無感打印
- 2022-05-19面試之JS篇
- 2022-05-18vue axios 賦值后console可以查看到 html調用數據失敗
- 2022-05-18vue安裝
- 2022-05-18js 閉包的認知提升