解析擴展元素
之前我們看的都是Activiti對自有元素的解析,比如UserTask、Process、StartEvent等,而在Activiti中,我們也可以對自定義元素進行解析。對自定義元素的解析是通過org.activiti.bpmn.converter.util.BpmnXMLUtil.parseExtensionElement
進行解析的。解析代碼如下
public static ExtensionElement parseExtensionElement(XMLStreamReader xtr) throws Exception {
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setName(xtr.getLocalName());
if (StringUtils.isNotEmpty(xtr.getNamespaceURI())) {
extensionElement.setNamespace(xtr.getNamespaceURI());
}
if (StringUtils.isNotEmpty(xtr.getPrefix())) {
extensionElement.setNamespacePrefix(xtr.getPrefix());
}
for (int i = 0; i < xtr.getAttributeCount(); i++) {
ExtensionAttribute extensionAttribute = new ExtensionAttribute();
extensionAttribute.setName(xtr.getAttributeLocalName(i));
extensionAttribute.setValue(xtr.getAttributeValue(i));
if (StringUtils.isNotEmpty(xtr.getAttributeNamespace(i))) {
extensionAttribute.setNamespace(xtr.getAttributeNamespace(i));
}
if (StringUtils.isNotEmpty(xtr.getAttributePrefix(i))) {
extensionAttribute.setNamespacePrefix(xtr.getAttributePrefix(i));
}
extensionElement.addAttribute(extensionAttribute);
}
boolean readyWithExtensionElement = false;
while (readyWithExtensionElement == false && xtr.hasNext()) {
xtr.next();
if (xtr.isCharacters() || XMLStreamReader.CDATA == xtr.getEventType()) {
if (StringUtils.isNotEmpty(xtr.getText().trim())) {
extensionElement.setElementText(xtr.getText().trim());
}
} else if (xtr.isStartElement()) {
ExtensionElement childExtensionElement = parseExtensionElement(xtr);
extensionElement.addChildElement(childExtensionElement);
} else if (xtr.isEndElement() && extensionElement.getName().equalsIgnoreCase(xtr.getLocalName())) {
readyWithExtensionElement = true;
}
}
return extensionElement;
}
- 首先實例化擴展元素,以便獲取定義的信息
- 設置名稱、命名空間、命名空間前綴、定義的屬性(名稱、命名空間、命名空間前綴)
- 判斷是否結束,
- 沒有結束則再依次判斷
- 有文本節點,進行賦值
- 有子節點,則獲取子節點屬性
- 是對應的結束節點,跳出循環
比如說如下配置
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="test-worlfow" name="流程測試" activiti:key="${bussneKey}" activiti:processHandler="defaultTaskService">
<extensionElements>
<activiti:page url="ccccc"></activiti:page>
<activiti:bizConfig>
<item key="bizType" value="authorize"/>
</activiti:bizConfig>
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask1" />
<userTask id="userTask1" name="任務1" >
<extensionElements>
<activiti:toolbar >
<activiti:button type="general" name="submit" displayName="提交" targetRef="agree" enable="true">
<activiti:user policy="role" para="000100" enable="${true}"></activiti:user>
</activiti:button>
</activiti:toolbar>
<activiti:bizConfig>
<item key="stepType" value="待提交"/>
<item key="other" value="xxx"/>
</activiti:bizConfig>
<activiti:page url="/law/${1}.json?"></activiti:page>
</extensionElements>
</userTask>
<sequenceFlow id="flow2" sourceRef="userTask1" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
以下是測試解析擴展元素的方法
public class TestParseExtensionElement {
public static void main(String[] args) {
String resource = "test-workflow.bpmn20.xml";
InputStream xmlStream = TestParseExtensionElement.class.getClassLoader()
.getResourceAsStream(resource);
InputStreamSource xmlSource = new InputStreamSource(xmlStream);
BpmnXMLConverter converter = new BpmnXMLConverter();
BpmnModel model = converter.convertToBpmnModel(xmlSource, true, true);
Process process = model.getMainProcess();
Map<String, List<ExtensionElement>> extensionElements = process.getExtensionElements();
displayElementAndChild(extensionElements);
}
private static void displayElementAndChild(
Map<String, List<ExtensionElement>> extensionElements) {
for (Entry<String, List<ExtensionElement>> entry : extensionElements.entrySet()) {
List<ExtensionElement> values = entry.getValue();
for (ExtensionElement e : values) {
System.out.println("key : " + e.getName());
displayAttribute(e);
Map<String, List<ExtensionElement>> child = e.getChildElements();
if (child == null || child.isEmpty()) {
continue;
}
displayElementAndChild(child);
}
}
}
private static void displayAttribute(ExtensionElement extensionElement) {
Map<String, List<ExtensionAttribute>> attributes = extensionElement.getAttributes();
Collection<List<ExtensionAttribute>> attrValues = attributes.values();
if (attrValues == null || attrValues.isEmpty()) {
return;
}
System.out.println(extensionElement.getName() + " attributes:");
System.out.print("\t");
for (List<ExtensionAttribute> list : attrValues) {
for (ExtensionAttribute attr : list) {
System.out.print(attr.getName() + ":" + attr.getValue() + ", ");
}
}
System.out.println();
}
}
- 首先我們讀取上文中提到的流程配置文件
- 調用BpmnXMLConverter中的converToBpmnModel方法獲取BpmnModel對象
- displayElementAndChild方法打印輸出元素以及子元素的信息
- displayAttribute方法輸出自定義屬性信息
解析自定義屬性
在之前的的幾個轉換類中,我們發現大部分都有要解析自定義屬性,可以簡單看一下對自定義屬性的解析,在org.activiti.bpmn.converter.util.BpmnXMLUtil
類中,
/**
* 將所有的擴展屬性添加到元素中,並忽略blackLists中的擴展屬性
*
* @param xtr
* @param element
* @param blackList 黑名單
*/
public static void addCustomAttributes(XMLStreamReader xtr, BaseElement element, List<ExtensionAttribute>... blackLists) {
for (int i = 0; i < xtr.getAttributeCount(); i++) {
ExtensionAttribute extensionAttribute = new ExtensionAttribute();
extensionAttribute.setName(xtr.getAttributeLocalName(i));
extensionAttribute.setValue(xtr.getAttributeValue(i));
if (StringUtils.isNotEmpty(xtr.getAttributeNamespace(i))) {
extensionAttribute.setNamespace(xtr.getAttributeNamespace(i));
}
if (StringUtils.isNotEmpty(xtr.getAttributePrefix(i))) {
extensionAttribute.setNamespacePrefix(xtr.getAttributePrefix(i));
}
if (!isBlacklisted(extensionAttribute, blackLists)) {
element.addAttribute(extensionAttribute);
}
}
}
- 解析擴展屬性比較簡單,基本邏輯就是將name、value、namespace等屬性解析
- 忽略黑名單中的屬性
黑名單屬性
-
全局屬性
| 屬性| 含義|
| – | --|
| id |id,全局唯一 |
| name|任務節點的名稱 | -
activiti屬性
| 屬性| 命名空間 | 含義|
| – | --| --|
| aysn|http://activiti.org/bpmn
|是否異步執行|
| exclusive|http://activiti.org/bpmn
|是否排他|
| default||默認屬性|
| isForCompensation|http://activiti.org/bpmn
|是否補償| -
元素特有屬性(userTask的屬性,process特有的屬性)
UserTask特有屬性,其命名空間都是http://activiti.org/bpmn
屬性 | 含義 |
---|---|
formKey | 任務節點關聯的form表單 |
dueDate | 任務截止日期 |
businessCalendarName | 業務日曆名稱 |
assignee | 任務執行人 |
owner | 任務所有者 |
priority | 優先級 |
candidateUsers | 候選執行人 |
candidateGroups | 候選執行人分鐘 |
category | 分類 |
extensionId | 擴展id |
skipExpression | 跳過任務表達式 |
Process屬性,除了公共屬性,之外還有一下屬性,其命名空間都是http://activiti.org/bpmn
屬性 | 含義 |
---|---|
isExecutable | 是否可執行單 |
candidateStarterUsers | 候選流程啓動執行人 |
candidateStarterGroups | 候選流程啓動分組 |
解析子元素
各個元素的轉換器首先會調用抽象類BaseBpmnXMLConverter中的parseChildElements
方法
protected void parseChildElements(String elementName, BaseElement parentElement, BpmnModel model, XMLStreamReader xtr) throws Exception {
parseChildElements(elementName, parentElement, null, model, xtr);
}
protected void parseChildElements(String elementName, BaseElement parentElement, Map<String, BaseChildElementParser> additionalParsers, BpmnModel model, XMLStreamReader xtr) throws Exception {
Map<String, BaseChildElementParser> childParsers = new HashMap<String, BaseChildElementParser>();
if (additionalParsers != null) {
childParsers.putAll(additionalParsers);
}
BpmnXMLUtil.parseChildElements(elementName, parentElement, xtr, childParsers, model);
}
然後再調用org.activiti.bpmn.converter.util.BpmnXMLUtil.parseChildElements
方法,
public static void parseChildElements(String elementName, BaseElement parentElement, XMLStreamReader xtr,
Map<String, BaseChildElementParser> childParsers, BpmnModel model) throws Exception {
Map<String, BaseChildElementParser> localParserMap =
new HashMap<String, BaseChildElementParser>(genericChildParserMap);
if (childParsers != null) {
localParserMap.putAll(childParsers);
}
boolean inExtensionElements = false;
boolean readyWithChildElements = false;
while (readyWithChildElements == false && xtr.hasNext()) {
xtr.next();
if (xtr.isStartElement()) {
if (ELEMENT_EXTENSIONS.equals(xtr.getLocalName())) {
inExtensionElements = true;
} else if (localParserMap.containsKey(xtr.getLocalName())) {
BaseChildElementParser childParser = localParserMap.get(xtr.getLocalName());
//if we're into an extension element but the current element is not accepted by this parentElement then is read as a custom extension element
if (inExtensionElements && !childParser.accepts(parentElement)) {
ExtensionElement extensionElement = BpmnXMLUtil.parseExtensionElement(xtr);
parentElement.addExtensionElement(extensionElement);
continue;
}
localParserMap.get(xtr.getLocalName()).parseChildElement(xtr, parentElement, model);
} else if (inExtensionElements) {
ExtensionElement extensionElement = BpmnXMLUtil.parseExtensionElement(xtr);
parentElement.addExtensionElement(extensionElement);
}
} else if (xtr.isEndElement()) {
if (ELEMENT_EXTENSIONS.equals(xtr.getLocalName())) {
inExtensionElements = false;
} else if (elementName.equalsIgnoreCase(xtr.getLocalName())) {
readyWithChildElements = true;
}
}
}
}
通過以上源碼我們可以看到解析子元素的步驟
- 首先初始化子元素解析器集合,包括通用的子元素的解析器以及每個元素單獨適用的的子元素解析器
- 解析擴展元素
a) 如果判斷元素屬於子元素解析器集合中的類,則直接調用子元素解析
b) 如果元素是擴展元素(extensionElement),則調用BpmnXMLUtil.parseExtensionElement(xtr)
進行解析 - 有一個額外判斷,如果該元素是擴展元素,並且父類不能接受這個擴展元素,則它被解析爲一個擴展元素
以解析userTask元素爲例,
首先在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);
以下是org.activiti.bpmn.converter.UserTaskXMLConverter.convertXMLToElement
的部分代碼
protected BaseElement convertXMLToElement(XMLStreamReader xtr, BpmnModel model) throws Exception {
....
parseChildElements(getXMLElementName(), userTask, childParserMap, model, xtr);
return userTask;
}
可以看出最終會將特有子元素解析器傳入並調用父類的方法實現。