写在前面:博主实在抽不出时间准备demo,由于你们有需求,先把文章发给你们看
Hello 你们好,我是易样(容易不同,咱们不同,一天一个样)。前端
很久没更新文章了,没更新文章的这些时间我都在闭关修炼,努力提高自身技术,毕竟我2020年的flag是成为大牛。vue
今天给你们带来的这篇文章是整理我使用bpmn-js实现activiti流程设计器的经验之谈,bpmn-js的中文文档很少,不少人都不如何入手开发,而且bpmn-js的后端使用的是Camunda,如何使用activiti也是困扰了不少开发。java
不要怕,让小姐姐来教教你。node
须要先后端分离、以为activiti的设计器很差用、使用bpmn-js实现设计器,但后端用的是activiti,xml不兼容怎么办?git
不止我这一种解决方法,我这里只提供个人解决方法。github
关于bpmn-js如何使用建议搭建去github上面搜索,这里贴上官网地址: github.com/bpmn-io/bpm…spring
官网案例地址:github.com/bpmn-io/bpm…数据库
笔者开发设计器时参考了霖呆呆的关于bpmn-js从0开发的一系列文章,地址: juejin.im/post/5def37…express
相信你们看完以上我贴的文章,对bpmn-js已经很熟悉了;接下来我来解释一下个人项目:apache
如图,是我彻底自定义的属性面板
部分代码以下:
<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,不要辜负个人良苦用心呀
因为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
我项目设计器设计的流程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>复制代码
节点里的属性大部分都是我自定义的属性
具体怎么搭建activiti环境,相信你们都能百度到,我只介绍怎么将bpmn-js和activiti兼容
如图,展现了一个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下存方着全部属性
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 + "配置成了会签,但没有配置经过权重");
}
}复制代码
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);
}
}
}
}
}
}
}
复制代码