
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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy