All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.effektif.workflow.impl.bpmn.BpmnReaderImpl Maven / Gradle / Ivy

/* Copyright (c) 2014, Effektif GmbH.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. */
package com.effektif.workflow.impl.bpmn;

import static com.effektif.workflow.impl.bpmn.Bpmn.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.stream.Collectors;

import com.effektif.workflow.api.bpmn.XmlNamespaces;
import com.effektif.workflow.api.workflow.*;
import com.effektif.workflow.impl.workflow.boundary.BoundaryEventTimer;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.effektif.workflow.api.bpmn.BpmnReadable;
import com.effektif.workflow.api.bpmn.BpmnReader;
import com.effektif.workflow.api.bpmn.XmlElement;
import com.effektif.workflow.api.condition.Condition;
import com.effektif.workflow.api.model.Id;
import com.effektif.workflow.api.model.RelativeTime;
import com.effektif.workflow.api.types.DataType;
import com.effektif.workflow.api.workflow.diagram.Bounds;
import com.effektif.workflow.api.workflow.diagram.Diagram;
import com.effektif.workflow.api.workflow.diagram.Edge;
import com.effektif.workflow.api.workflow.diagram.Node;
import com.effektif.workflow.api.workflow.diagram.Point;
import com.effektif.workflow.impl.exceptions.BadRequestException;
import com.effektif.workflow.impl.json.JsonObjectReader;
import com.effektif.workflow.impl.json.JsonStreamMapper;
import com.effektif.workflow.impl.json.JsonTypeMapper;
import com.effektif.workflow.impl.json.PolymorphicMapping;
import com.effektif.workflow.impl.json.TypeMapping;
import com.effektif.workflow.impl.json.types.LocalDateTimeStreamMapper;

/**
 * This implementation of the BPMN reader is based on reading single values from XML elements and attributes into
 * single-valued (mostly primitive) types. Complex types and arbitrary Java beans are not supported
 *
 * To support complex types in the future, a preferable alternative to implementing an bean mapping framework will be to
 * leverage the existing JSON mapping implementation, which supports nested structures, and read complex objects from
 * JSON embedded in CDATA sections in the BPMN. For example, something like:
 *
 * 
 *   
 *     
 *   
 * 
* * TODO Refactor to make reading use a more consistent API than the current read methods: * a mix between model class readBpmn methods, and read* methods in this class with inconsistent parameter lists. * * @author Tom Baeyens */ public class BpmnReaderImpl implements BpmnReader { private static final Logger log = LoggerFactory.getLogger(BpmnReaderImpl.class); /** global mappings */ protected BpmnMappings bpmnMappings; /** stack of scopes */ protected Stack scopeStack = new Stack(); /** current scope */ protected Scope scope; /** stack of xml elements */ protected Stack xmlStack = new Stack(); /** current xml element */ protected XmlElement currentXml; protected Class currentClass; protected JsonStreamMapper jsonStreamMapper; public BpmnReaderImpl(BpmnMappings bpmnMappings, JsonStreamMapper jsonStreamMapper) { this.bpmnMappings = bpmnMappings; this.jsonStreamMapper = jsonStreamMapper; } /** * The BPMN definitions element includes the document’s XML namespace declarations. * Ideally, namespaces should be read in a stack, so that each element can add new namespaces. * The addPrefixes() should then be refactored to pushPrefixes and popPrefixes. * The current implementation assumes that all namespaces are defined in the root element. */ protected AbstractWorkflow readDefinitions(XmlElement definitionsXml) { AbstractWorkflow workflow = null; if (definitionsXml.elements != null) { Iterator iterator = definitionsXml.elements.iterator(); while (iterator.hasNext()) { XmlElement definitionElement = iterator.next(); boolean processAlreadyParsed = workflow != null; if (definitionElement.is(BPMN_URI, "process") && !processAlreadyParsed) { iterator.remove(); workflow = readWorkflow(definitionElement); } } } if (workflow == null) { workflow = new ExecutableWorkflow(); } readDiagram(workflow, definitionsXml); definitionsXml.cleanEmptyElements(); workflow.property(KEY_DEFINITIONS, definitionsXml); return workflow; } protected AbstractWorkflow readWorkflow(XmlElement processXml) { AbstractWorkflow workflow = new ExecutableWorkflow(); this.currentXml = processXml; this.scope = workflow; workflow.readBpmn(this); attachTimers(workflow); readLanes(workflow); removeDanglingTransitions(workflow); setUnparsedBpmn(workflow, processXml); workflow.cleanUnparsedBpmn(); return workflow; } protected void attachTimers(AbstractWorkflow workflow) { List timers = workflow.getTimers(); if (timers != null && timers.size() > 0) { Iterator timerIterator = timers.iterator(); while (timerIterator.hasNext()) { Timer timer = timerIterator.next(); // todo: make generic if (timer instanceof BoundaryEventTimer) { BoundaryEvent boundaryEvent = ((BoundaryEventTimer) timer).boundaryEvent; if (boundaryEvent != null) { Activity act = workflow.findActivity(boundaryEvent.getFromId()); if (act != null) act.timer(timer); timerIterator.remove(); } } } } } protected void readLanes(AbstractWorkflow workflow) { // Not supported. } public void readScope() { if (currentXml.elements!=null) { Iterator iterator = currentXml.elements.iterator(); while (iterator.hasNext()) { XmlElement scopeElement = iterator.next(); startElement(scopeElement); if (scopeElement.is(BPMN_URI, "extensionElements")) { scope.setProperties(readSimpleProperties()); } else if (scopeElement.is(BPMN_URI, "sequenceFlow")) { Transition transition = new Transition(); transition.readBpmn(this); scope.transition(transition); // Remove the sequenceFlow as it has been parsed in the model. iterator.remove(); } else if (scopeElement.is(BPMN_URI, "boundaryEvent")) { // // SequenceFlow_0se37xg // // PT5M // // // startElement(scopeElement); BoundaryEvent boundaryEvent = new BoundaryEvent(); boundaryEvent.readBpmn(this); for (XmlElement xmlElement : currentXml.getElements()) { BpmnTypeMapping typeMapping = bpmnMappings.getBpmnTypeMapping(xmlElement, this); if (typeMapping != null) { BoundaryEventTimer timer = new BoundaryEventTimer(); startElement(xmlElement); timer.readBpmn(this); timer.boundaryEvent = boundaryEvent; endElement(); scope.timer(timer); } } iterator.remove(); endElement(); } else { BpmnTypeMapping bpmnTypeMapping = getBpmnTypeMapping(); if (bpmnTypeMapping != null) { Activity activity = (Activity) bpmnTypeMapping.instantiate(); // read the fields activity.readBpmn(this); scope.activity(activity); setUnparsedBpmn(activity, currentXml); activity.cleanUnparsedBpmn(); // Remove the activity XML element as it has been parsed in the model. iterator.remove(); } } endElement(); } currentXml.removeEmptyElement(BPMN_URI, "extensionElements"); } } /** * Check if the XML element can be parsed as one of the activity types. */ protected BpmnTypeMapping getBpmnTypeMapping() { return bpmnMappings.getBpmnTypeMapping(currentXml, this); } protected void setUnparsedBpmn(Scope scope, XmlElement unparsedBpmn) { unparsedBpmn.name = null; scope.setBpmn(unparsedBpmn); } @Override public List readElementsBpmn(String localPart) { if (currentXml==null) { return Collections.EMPTY_LIST; } return currentXml.removeElements(BPMN_URI, localPart); } @Override public List readElementsEffektif(Class modelClass) { BpmnTypeMapping bpmnTypeMapping = bpmnMappings.getBpmnTypeMapping(modelClass); String localPart = bpmnTypeMapping.getBpmnElementName(); return readElementsEffektif(localPart); } @Override public List readElementsEffektif(String localPart) { if (currentXml==null) { return Collections.EMPTY_LIST; } return currentXml.removeElements(EFFEKTIF_URI, localPart); } @Override public XmlElement readElementEffektif(String localPart) { if (currentXml==null) { return null; } List xmlElements = currentXml.removeElements(EFFEKTIF_URI, localPart); return !xmlElements.isEmpty() ? xmlElements.get(0) : null; } @Override public void startElement(XmlElement xmlElement) { if (currentXml!=null) { xmlStack.push(currentXml); } currentXml = xmlElement; } @Override public void endElement() { currentXml = xmlStack.empty() ? null : xmlStack.pop(); } public void startScope(Scope scope) { if (this.scope!=null) { scopeStack.push(this.scope); } this.scope = scope; } public void endScope() { this.scope = scopeStack.pop(); } @Override public void startExtensionElements() { XmlElement extensionsXmlElement = currentXml.getElement(BPMN_URI, "extensionElements"); startElement(extensionsXmlElement); } @Override public void endExtensionElements() { endElement(); currentXml.removeEmptyElement(BPMN_URI, "extensionElements"); } @Override public Boolean readBooleanAttributeEffektif(String localPart) { if (currentXml==null) { return null; } String booleanStringValue = currentXml.removeAttribute(BPMN_URI, localPart); if (booleanStringValue==null) { return null; } return Boolean.valueOf(booleanStringValue); } @Override public String readStringAttributeBpmn(String localPart) { if (currentXml==null) { return null; } return currentXml.removeAttribute(BPMN_URI, localPart); } @Override public String readStringAttributeEffektif(String localPart) { if (currentXml==null) { return null; } return currentXml.removeAttribute(EFFEKTIF_URI, localPart); } @Override public T readIdAttributeBpmn(String localPart, Class idType) { if (currentXml==null) { return null; } return toId(readStringAttributeBpmn(localPart), idType); } @Override public T readIdAttributeEffektif(String localPart, Class idType) { if (currentXml==null) { return null; } return toId(readStringAttributeEffektif(localPart), idType); } @Override public Integer readIntegerAttributeEffektif(String localPart) { if (currentXml==null) { return null; } String valueString = readStringAttributeEffektif(localPart); try { return new Integer(valueString); } catch (NumberFormatException e) { return null; } } @Override public Binding readBinding(Class modelClass, Class type) { BpmnTypeMapping bpmnTypeMapping = bpmnMappings.getBpmnTypeMapping(modelClass); String localPart = bpmnTypeMapping.getBpmnElementName(); return readBinding(localPart, type); } /** Returns a binding from the first extension element with the given name. */ @Override public Binding readBinding(String localPart, Class type) { if (currentXml==null) { return null; } List> bindings = readBindings(localPart); if (bindings.isEmpty()) { return new Binding(); } else { return bindings.get(0); } } /** Returns a list of bindings from the extension elements with the given name. */ @Override public List> readBindings(String localPart) { if (currentXml==null) { return null; } List> bindings = new ArrayList<>(); for (XmlElement element: currentXml.removeElements(EFFEKTIF_URI, localPart)) { Binding binding = new Binding(); String value = element.getAttribute(EFFEKTIF_URI, "value"); String typeName = element.getAttribute(EFFEKTIF_URI, "type"); startElement(element); XmlElement metadataElement = readElementEffektif("metadata"); Map metadata = null; if (metadataElement != null) { startElement(metadataElement); metadata = readSimpleProperties(); endElement(); } endElement(); DataType type = convertType(typeName); binding.setValue(parseText(value, (Class) type.getValueType())); binding.setExpression(element.getAttribute(EFFEKTIF_URI, "expression")); binding.setMetadata(metadata); bindings.add(binding); } return bindings; } @SuppressWarnings("unchecked") protected T parseText(String value, Class type) { if (value==null) { return null; } if (type==String.class) { return (T) value; } if (type==Boolean.class) { return (T) Boolean.valueOf(value); } if (type==Double.class) { return (T) Double.valueOf(value); } if (type==Long.class) { return (T) Long.valueOf(value); } if (Id.class.isAssignableFrom(type)) { return (T) toId(value, (Class) type); } if (type==LocalDateTime.class) { return (T) LocalDateTimeStreamMapper.PARSER.parseLocalDateTime(value); } if (type==Number.class) { return (T) Double.valueOf(value); } // Use a registered JSON type mapper to parse the value. JsonObjectReader jsonReader = new JsonObjectReader(bpmnMappings); JsonTypeMapper typeMapper = bpmnMappings.getTypeMapper(type); return (T) typeMapper.read(value, jsonReader); } /** * Returns an ID type instance, constructed from the given JSON string ID. */ private static final Class< ? >[] ID_CONSTRUCTOR_PARAMETERS = new Class< ? >[] { String.class }; public static T toId(Object jsonId, Class idType) { if (jsonId==null) { return null; } try { jsonId = jsonId.toString(); Constructor c = idType.getDeclaredConstructor(ID_CONSTRUCTOR_PARAMETERS); return (T) c.newInstance(new Object[] { jsonId }); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } /** Returns the contents of the BPMN documentation element. */ @Override public String readDocumentation() { if (currentXml==null) { return null; } XmlElement documentationElement = currentXml.removeElement(BPMN_URI, "documentation"); if (documentationElement!=null) { return documentationElement.getText(); } return null; } @Override public Trigger readTriggerEffektif() { try { PolymorphicMapping triggerMapping = bpmnMappings.getPolymorphicMapping(Trigger.class); TypeMapping triggerSubclassMapping = triggerMapping.getTypeMapping(this); Trigger type = (Trigger) triggerSubclassMapping.instantiate(); type.readBpmn(this); return type; } catch (Exception e) { throw new RuntimeException(e); } } @Override public T readPolymorphicEffektif(XmlElement xmlElement, Class type) { try { startElement(xmlElement); PolymorphicMapping polymorphicMapping = bpmnMappings.getPolymorphicMapping(type); TypeMapping subclassMapping = polymorphicMapping.getTypeMapping(this); T object = (T) subclassMapping.instantiate(); object.readBpmn(this); endElement(); return object; } catch (Exception e) { throw new RuntimeException(e); } } @Override public DataType readTypeAttributeEffektif() { return readTypeAttributeEffektif("type"); } private DataType readTypeAttributeEffektif(String attributeName) { String typeName = readStringAttributeEffektif(attributeName); return convertType(typeName); } @Override public LocalDateTime readDateAttributeEffektif(String attributeName) { String dateStringName = readStringAttributeEffektif(attributeName); return dateStringName!=null ? LocalDateTimeStreamMapper.PARSER.parseLocalDateTime(dateStringName) : null; } private DataType convertType(String typeName) { if (typeName == null) { typeName = "text"; } PolymorphicMapping dataTypeMapping = bpmnMappings.getPolymorphicMapping(DataType.class); DataType type = (DataType) dataTypeMapping.getTypeMapping(typeName).instantiate(); type.readBpmn(this); return type; } @Override public DataType readTypeElementEffektif() { XmlElement typeElement = readElementEffektif("type"); DataType type = null; if (typeElement!=null) { startElement(typeElement); type = readTypeAttributeEffektif("name"); endElement(); } return type; } @Override public RelativeTime readRelativeTimeEffektif(String localPart) { RelativeTime relativeTime = null; XmlElement element = currentXml != null ? currentXml.removeElement(EFFEKTIF_URI, localPart) : null; if (element != null) { startElement(element); relativeTime = RelativeTime.readBpmnPolymorphic(this); endElement(); } return relativeTime; } @Override public LocalDateTime readDateValue(String localPart) { XmlElement element = currentXml != null ? currentXml.removeElement(EFFEKTIF_URI, localPart) : null; if (element != null) { String value = element.getAttribute(EFFEKTIF_URI, "value"); if (value != null) { return LocalDateTimeStreamMapper.PARSER.parseLocalDateTime(value); } } return null; } /** * Reads nested property elements: currently only String, Boolean, Integer and Double properties are supported. */ @Override public Map readSimpleProperties() { Map properties = new HashMap<>(); for (XmlElement element : readElementsEffektif("property")) { startElement(element); String key = readStringAttributeEffektif("key"); String value = readStringAttributeEffektif("value"); String type = readStringAttributeEffektif("type"); if (key != null && value != null && type != null) { try { if (String.class.getName().equals(type)) { properties.put(key, value.toString()); } else if (Boolean.class.getName().equals(type)) { properties.put(key, Boolean.valueOf(value)); } else if (Integer.class.getName().equals(type)) { properties.put(key, Integer.valueOf(value)); } else if (Double.class.getName().equals(type)) { properties.put(key, Double.valueOf(value)); } else { log.warn(String.format("Unsupported property type ‘%s’ for property %s=%s", type, key, value)); } } catch (NumberFormatException e) { log.warn(String.format("Unsupported value format for type ‘%s’ for property %s=%s", type, key, value)); } } endElement(); } return properties; } @Override public String readStringValue(String localPart) { XmlElement element = currentXml != null ? currentXml.removeElement(EFFEKTIF_URI, localPart) : null; if (element != null) { return element.getAttribute(EFFEKTIF_URI, "value"); } return null; } @Override public String readTextBpmn(String localPart) { return readText(BPMN_URI, localPart); } @Override public String readTextEffektif(String localPart) { return readText(EFFEKTIF_URI, localPart); } private String readText(String namespaceUri, String localPart) { XmlElement textElement = currentXml!=null ? currentXml.removeElement(namespaceUri, localPart) : null; if (textElement!=null) { return textElement.getText(); } return null; } @Override public XmlElement getUnparsedXml() { return currentXml; } public Condition readCondition() { List conditions = readConditions(); if (conditions.size() > 0) { return conditions.get(0); } return null; } /** * Returns a list of {@link Condition} instances by using this reader to read BPMN for all of the condition types. */ @Override public List readConditions() { List conditions = new ArrayList<>(); SortedSet> bpmnClasses = bpmnMappings.getBpmnClasses(); for (Class bpmnClass : bpmnClasses) { if (Condition.class.isAssignableFrom(bpmnClass)) { try { Condition condition = (Condition) bpmnClass.newInstance(); condition.readBpmn(this); if (!condition.isEmpty()) { conditions.add(condition); } } catch (Exception e) { throw new RuntimeException("Could not read condition type " + bpmnClass.getName()); } } } return conditions; } /** * Removes transitions to or from a missing activity, probably due to the activity not being imported. */ private void removeDanglingTransitions(AbstractWorkflow workflow) { if (workflow.getTransitions() == null || workflow.getTransitions().isEmpty()) { return; } Set activityIds = new HashSet<>(); for (Activity activity : workflow.getActivities()) { activityIds.add(activity.getId()); // Transitions from Boundary event timers should be included as well // todo: make generic List activityTimers = activity.getTimers(); if (activityTimers != null) { for (Timer timer : activityTimers) { if (timer instanceof BoundaryEventTimer) { BoundaryEvent boundaryEvent = ((BoundaryEventTimer) timer).boundaryEvent; activityIds.add(boundaryEvent.getBoundaryId()); activityIds.addAll(boundaryEvent.getToTransitionIds()); } } } } ListIterator transitionIterator = workflow.getTransitions().listIterator(); while(transitionIterator.hasNext()){ Transition transition = transitionIterator.next(); if (!activityIds.contains(transition.getFromId()) || !activityIds.contains(transition.getToId())) { transitionIterator.remove(); } } } /** * Reads the workflow name, description and diagram from BPMN. */ private void readDiagram(AbstractWorkflow workflow, XmlElement definitionsXml) { if (definitionsXml==null) { return; } for (XmlElement diagramElement: definitionsXml.removeElements(BPMN_DI_URI, "BPMNDiagram")) { startElement(diagramElement); if (currentXml==null) { return; } workflow.setName(currentXml.removeAttribute(BPMN_DI_URI, "name")); if (workflow.getDescription() == null) { workflow.setDescription(currentXml.removeAttribute(BPMN_DI_URI, "documentation")); } Diagram diagram = new Diagram(); for (XmlElement planeElement: diagramElement.removeElements(BPMN_DI_URI, "BPMNPlane")) { List shapes = readShapes(planeElement); diagram.addNodes(shapes); if (workflow.getTransitions() != null) { diagram.edges(readEdges(shapes, workflow.getTransitions(), planeElement)); } } // Reference the process we’re importing from the diagram, setting it directly because the BPMNPlane/@elementId // may refer to multiple processes via definitions/collaboration and its nested participants. if (workflow.getId() != null) { diagram.canvas.elementId = workflow.getId().getInternal(); } workflow.setDiagram(diagram); removeOrphanedDiagramElements(workflow, definitionsXml); endElement(); } } /** * Removes diagram shapes that don’t correspond to an imported workflow activity, such as those not supported. */ private void removeOrphanedDiagramElements(AbstractWorkflow workflow, XmlElement definitionsXml) { Diagram diagram = workflow.getDiagram(); if (diagram == null || !diagram.hasChildren()) { return; } // Collect valid participant (pool) IDs. Set participantIds = findParticipantIds(definitionsXml); participantIds.forEach(id -> log.debug("POOL = " + id)); // Collect valid activity IDs and variable IDs, which lane IDs are mapped to. Set activityIds = workflow.getActivities() == null ? new HashSet<>() : workflow.getActivities().stream().map(activity -> activity.getId()).collect(Collectors.toSet()); Set variableIds = workflow.getVariables() == null ? new HashSet<>() : workflow.getVariables().stream().map(variable -> variable.getId()).collect(Collectors.toSet()); // Remove orphaned shapes/nodes. Set shapeIds = new HashSet<>(); if (diagram.hasChildren()) { Iterator shapeIterator = diagram.canvas.children.iterator(); while (shapeIterator.hasNext()) { Node shape = shapeIterator.next(); // Keep shapes for lanes by checking against variable IDs, since lanes are the only shapes mapped to variables. boolean poolShape = participantIds.contains(shape.elementId); boolean laneShape = activityIds.contains(shape.elementId) || variableIds.contains(shape.elementId); if (!poolShape && !laneShape) { shapeIterator.remove(); } } // Collect valid shape IDs. for (Node shape : diagram.canvas.children) { shapeIds.add(shape.id); } } // Remove orphaned edges. if (diagram.hasEdges()) { Iterator edgeIterator = diagram.edges.iterator(); while (edgeIterator.hasNext()) { Edge edge = edgeIterator.next(); boolean transitionDefined = workflow.findTransition(edge.transitionId) != null; boolean edgeValid = shapeIds.contains(edge.fromId) && shapeIds.contains(edge.toId) && transitionDefined; if (!edgeValid) { edgeIterator.remove(); } } } } private Set findParticipantIds(XmlElement definitions) { Set ids = definitions == null ? new HashSet<>() : definitions.elements.stream() .filter(element -> element.name.equals("collaboration")) .flatMap(collaboration -> collaboration.elements.stream()) .filter(element -> element.name.equals("participant")) .map(participant -> participant.getAttribute(BPMN_URI, "id")) .collect(Collectors.toSet()); return ids; } private List readShapes(XmlElement planeElement) { List nodes = new ArrayList<>(); for (XmlElement shapeElement: planeElement.removeElements(BPMN_DI_URI, "BPMNShape")) { startElement(shapeElement); String id = currentXml.removeAttribute(BPMN_DI_URI, "id"); String elementId = currentXml.removeAttribute(BPMN_DI_URI, "bpmnElement"); Node node = new Node() .id(id) .elementId(elementId); // Read the optional BPMN attribute that indicates lane orientation. String horizontal = currentXml.removeAttribute(BPMN_DI_URI, "isHorizontal"); if (horizontal != null) { node.horizontal(horizontal.equals("true")); } String expanded = currentXml.removeAttribute(BPMN_DI_URI, "isExpanded"); if (expanded != null) { node.expanded(expanded.equals("true")); } for (XmlElement boundsElement: shapeElement.removeElements(OMG_DC_URI, "Bounds")) { startElement(boundsElement); double x = Double.valueOf(currentXml.removeAttribute(OMG_DC_URI, "x")); double y = Double.valueOf(currentXml.removeAttribute(OMG_DC_URI, "y")); double width = Double.valueOf(currentXml.removeAttribute(OMG_DC_URI, "width")); double height = Double.valueOf(currentXml.removeAttribute(OMG_DC_URI, "height")); node.bounds(new Bounds(new Point(x, y), width, height)); nodes.add(node); endElement(); } endElement(); } return nodes; } /** * Returns a list of edges read from sequenceFlow element transitions, and BPMNEdge coordinates. */ private List readEdges(List shapes, List transitions, XmlElement planeElement) { Map edgesBySequenceFlowId = readEdgesBySequenceFlowId(planeElement); // Map shape activity IDs to shape IDs, which are needed for edge from/to IDs. Map nodeIdByActivityId = new HashMap<>(); for (Node shape : shapes) { nodeIdByActivityId.put(shape.elementId, shape.id); } // Add node IDs from the previously-parsed workflow transitions and diagram nodes. List edges = new ArrayList<>(); for (Transition transition : transitions) { String sequenceFlowId = transition.getId(); Edge edge = edgesBySequenceFlowId.get(sequenceFlowId); if (edge==null) { BadRequestException.checkNotNull(edge, "No edge for sequenceFlow " + sequenceFlowId); } edge.fromId(nodeIdByActivityId.get(transition.getFromId())); edge.toId(nodeIdByActivityId.get(transition.getToId())); edges.add(edge); } return edges; } private Map readEdgesBySequenceFlowId(XmlElement planeElement) { Map edges = new HashMap<>(); for (XmlElement edgeElement: planeElement.removeElements(BPMN_DI_URI, "BPMNEdge")) { startElement(edgeElement); List edgeWaypoints = new ArrayList<>(); for (XmlElement pointElement: edgeElement.removeElements(OMG_DI_URI, "waypoint")) { startElement(pointElement); double x = Double.valueOf(currentXml.removeAttribute(OMG_DI_URI, "x")); double y = Double.valueOf(currentXml.removeAttribute(OMG_DI_URI, "y")); edgeWaypoints.add(new Point(x, y)); endElement(); } String id = currentXml.removeAttribute(BPMN_DI_URI, "id"); String sequenceFlowId = currentXml.removeAttribute(BPMN_DI_URI, "bpmnElement"); Edge edge = new Edge().id(id).transitionId(sequenceFlowId).dockers(edgeWaypoints); edges.put(sequenceFlowId, edge); endElement(); } return edges; } }