使用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.zyiz.net/tech/detail-120072.html

上一篇:【React 高级进阶】探索 store 设计、从零实现 react-redux(万字长文)

下一篇:一文了解服务端推送(含JS代码示例)

赞(0)

共有 条评论 网友评论

验证码: 看不清楚?
    关注微信小程序
    程序员编程王-随时随地学编程

    扫描二维码或查找【程序员编程王】

    可以随时随地学编程啦!

    技术文章导航 更多>
    扫一扫关注最新编程教程