package sushi.xml.importer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import sushi.bpmn.element.AbstractBPMNElement;
import sushi.bpmn.element.AttachableElement;
import sushi.bpmn.element.BPMNAndGateway;
import sushi.bpmn.element.BPMNBoundaryEvent;
import sushi.bpmn.element.BPMNEndEvent;
import sushi.bpmn.element.BPMNEventBasedGateway;
import sushi.bpmn.element.BPMNEventBasedGatewayType;
import sushi.bpmn.element.BPMNIntermediateEvent;
import sushi.bpmn.element.BPMNEventType;
import sushi.bpmn.element.BPMNProcess;
import sushi.bpmn.element.BPMNSequenceFlow;
import sushi.bpmn.element.BPMNStartEvent;
import sushi.bpmn.element.BPMNSubProcess;
import sushi.bpmn.element.BPMNTask;
import sushi.bpmn.element.BPMNXORGateway;
import sushi.bpmn.monitoringpoint.MonitoringPointStateTransition;
import sushi.bpmn.monitoringpoint.MonitoringPoint;
import sushi.event.SushiEventType;
/**
* This class generates a logical BPMN representation from a
* (Signavio-)BPMN-2.0-XML
*
* @author micha
*
*/
public class BPMNParser extends AbstractXMLParser {
private static ArrayList<String> VALID_BPMN_XML_ELEMENTS = new ArrayList<String>(Arrays.asList("startEvent", "task", "sendTask", "subProcess", "boundaryEvent", "endEvent", "parallelGateway", "exclusiveGateway", "intermediateCatchEvent", "eventBasedGateway"));
/**
* Parses a BPMN-2.0-XML from the given file path to a {@link BPMNProcess}.
* @param filePath
* @return
*/
public static BPMNProcess generateProcessFromXML(String filePath) {
Document doc = readXMLDocument(filePath);
return generateBPMNProcess(doc);
}
/**
* Parses a {@link BPMNProcess} from the {@link Document}.
* @param doc
* @return
*/
private static BPMNProcess generateBPMNProcess(Document doc) {
if(doc != null){
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new SignavioBPMNNameSpaceContext());
// XPath Query for showing all nodes value
XPathExpression processElementsExpression = null;
XPathExpression processExpression = null;
try {
processElementsExpression = xpath.compile("//ns:process/*");
processExpression = xpath.compile("//ns:process");
} catch (XPathExpressionException e) {
e.printStackTrace();
}
Object elementsResult = null;
Object processResult = null;
try {
elementsResult = processElementsExpression.evaluate(doc, XPathConstants.NODESET);
processResult = processExpression.evaluate(doc, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
e.printStackTrace();
}
NodeList processNode = (NodeList) processResult;
BPMNProcess process = new BPMNProcess(extractID(processNode.item(0)), "", new ArrayList<MonitoringPoint>());
NodeList processElements = (NodeList) elementsResult;
for (int i = 0; i < processElements.getLength(); i++) {
process.addBPMNElement(extractBPMNElement(processElements.item(i)));
}
linkProcessElements(process, processElements);
return process;
} else {
System.err.println("Document was null!");
return null;
}
}
/**
* Extraction of BPMN-Elements from the given {@link Node}. <br>
* Supported elements: <br>
* <ul>
* <li>boundaryEvent</li>
* <li>endEvent</li>
* <li>eventBasedGateway</li>
* <li>exclusiveGateway</li>
* <li>intermediateCatchEvent</li>
* <li>parallelGateway</li>
* <li>sequenceFlow</li>
* <li>startEvent</li>
* <li>subProcess</li>
* <li>task</li>
* </ul>
* @param element
* @return
*/
private static AbstractBPMNElement extractBPMNElement(Node element) {
if (element.getNodeName().equals("startEvent")) {
return extractStartEvent(element);
} else if (element.getNodeName().equals("task") || element.getNodeName().equals("sendTask")) {
return extractTask(element);
} else if (element.getNodeName().equals("subProcess")) {
return extractSubProcess(element);
} else if (element.getNodeName().equals("boundaryEvent")) {
return extractBoundaryEvent(element);
} else if (element.getNodeName().equals("endEvent")) {
return extractEndEvent(element);
} else if (element.getNodeName().equals("exclusiveGateway")) {
return extractExclusiveGateway(element);
} else if (element.getNodeName().equals("parallelGateway")) {
return extractParallelGateway(element);
} else if (element.getNodeName().equals("eventBasedGateway")) {
return extractEventBasedGateway(element);
} else if (element.getNodeName().equals("intermediateCatchEvent")) {
return extractIntermediateCatchEvent(element);
} else if (element.getNodeName().equals("sequenceFlow")) {
return extractSequenceFlow(element);
}
return null;
}
/**
* Parses an {@link BPMNEndEvent} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNEndEvent extractEndEvent(Node element) {
return new BPMNEndEvent(extractID(element), extractName(element), extractMonitoringPoints(element));
}
/**
* Parses an {@link BPMNBoundaryEvent} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNBoundaryEvent extractBoundaryEvent(Node element) {
BPMNBoundaryEvent boundaryEvent = new BPMNBoundaryEvent(extractID(element), extractName(element), extractMonitoringPoints(element), extractEventType(element));
boundaryEvent.setCancelActivity(extractCancelActivity(element));
switch(boundaryEvent.getIntermediateEventType()){
case Cancel:
break;
case Compensation:
break;
case Error:
break;
case Link:
break;
case Message:
break;
case Signal:
break;
case Timer:
extractEventTimerDefinition(element, boundaryEvent);
break;
default:
break;
}
return boundaryEvent;
}
/**
* Parses an {@link BPMNEventType} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNEventType extractEventType(Node element) {
if(!getChildNodesByNodeName(element, "timerEventDefinition").isEmpty()){
return BPMNEventType.Timer;
} else if(!getChildNodesByNodeName(element, "errorEventDefinition").isEmpty()){
return BPMNEventType.Error;
} else if(!getChildNodesByNodeName(element, "messageEventDefinition").isEmpty()){
return BPMNEventType.Message;
}
return BPMNEventType.Blank;
}
/**
* Parses the timer definition for the given intermediate event and assigns the definition to this element.
* @param element
* @param intermediateEvent
*/
private static void extractEventTimerDefinition(Node element, BPMNIntermediateEvent intermediateEvent) {
if(!getChildNodesByNodeName(element, "timerEventDefinition").isEmpty()){
List<Node> timerDefinitions = getChildNodesByNodeName(element, "timerEventDefinition");
for(Node timerDefinition : timerDefinitions){
//TimeDuration ermitteln
if(!getChildNodesByNodeName(timerDefinition, "timeDuration").isEmpty()){
String timeDuration = getChildNodesByNodeName(timerDefinition, "timeDuration").get(0).getTextContent();
if(timeDuration != null){
float duration;
try{
duration = Float.parseFloat(timeDuration);
} catch(NumberFormatException n){
System.err.println("Time duration could not be parsed!");
duration = 0;
}
intermediateEvent.setTimeDuration(duration);
}
}
}
}
}
/**
* Parses an {@link BPMNIntermediateEvent} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNIntermediateEvent extractIntermediateCatchEvent(Node element) {
BPMNIntermediateEvent intermediateEvent = new BPMNIntermediateEvent(extractID(element), extractName(element), extractMonitoringPoints(element), extractIntermediateEventType(element));
intermediateEvent.setCatchEvent(true);
if(intermediateEvent.getIntermediateEventType().equals(BPMNEventType.Timer)){
extractEventTimerDefinition(element, intermediateEvent);
}
return intermediateEvent;
}
/**
* Proofs, if the given {@link Node} has an cancelActivity attribute.
* @param element
* @return
*/
private static boolean extractCancelActivity(Node element) {
String cancelValue = element.getAttributes().getNamedItem("cancelActivity").getNodeValue();
boolean isCancelActivity = (cancelValue.equals("true")) ? true : false;
return isCancelActivity;
}
/**
* Parses an {@link BPMNSequenceFlow} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNSequenceFlow extractSequenceFlow(Node element) {
return new BPMNSequenceFlow(extractID(element), extractName(element), extractSourceRef(element), extractTargetRef(element));
}
/**
* Parses an {@link BPMNSubProcess} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNSubProcess extractSubProcess(Node element) {
BPMNSubProcess subProcess = new BPMNSubProcess(extractID(element), extractName(element), extractMonitoringPoints(element));
NodeList subProcessElements = (NodeList) element.getChildNodes();
for (int i = 0; i < subProcessElements.getLength(); i++) {
subProcess.addBPMNElement(extractBPMNElement(subProcessElements.item(i)));
}
return subProcess;
}
/**
* Parses an {@link BPMNTask} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNTask extractTask(Node element) {
List<MonitoringPoint> monitoringPoints = extractMonitoringPoints(element);
return new BPMNTask(extractID(element), extractName(element), monitoringPoints);
}
/**
* Parses an {@link BPMNAndGateway} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNAndGateway extractParallelGateway(Node element) {
List<MonitoringPoint> monitoringPoints = extractMonitoringPoints(element);
return new BPMNAndGateway(extractID(element), extractName(element), monitoringPoints);
}
/**
* Parses an {@link BPMNEventBasedGateway} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNEventBasedGateway extractEventBasedGateway(Node element) {
List<MonitoringPoint> monitoringPoints = extractMonitoringPoints(element);
return new BPMNEventBasedGateway(extractID(element), extractName(element), monitoringPoints, extractEventGatewayType(element));
}
/**
* Parses an {@link BPMNXORGateway} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNXORGateway extractExclusiveGateway(Node element) {
List<MonitoringPoint> monitoringPoints = extractMonitoringPoints(element);
return new BPMNXORGateway(extractID(element), extractName(element), monitoringPoints);
}
/**
* Parses an {@link BPMNStartEvent} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNStartEvent extractStartEvent(Node element) {
//TODO: MessageStartEvent unterstützen
return new BPMNStartEvent(extractID(element), extractName(element), extractMonitoringPoints(element), extractEventType(element));
}
/**
* Returns the value of the name attribute for the given {@link Node}.
* @param element
* @return
*/
private static String extractName(Node element) {
if(element.getAttributes().getNamedItem("name") != null){
return element.getAttributes().getNamedItem("name").getNodeValue();
} else{
return "";
}
}
/**
* Parses an {@link BPMNEventBasedGatewayType} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNEventBasedGatewayType extractEventGatewayType(Node element) {
String eventGatewayType = element.getAttributes().getNamedItem("eventGatewayType").getNodeValue();
if(eventGatewayType.equals("Parallel")){
return BPMNEventBasedGatewayType.Parallel;
} else {
return BPMNEventBasedGatewayType.Exclusive;
}
}
/**
* Returns the value of the sourceRef attribute for the given {@link Node}.
* @param element
* @return
*/
private static String extractSourceRef(Node element) {
return element.getAttributes().getNamedItem("sourceRef").getNodeValue();
}
/**
* Returns the value of the targetRef attribute for the given {@link Node}.
* @param element
* @return
*/
private static String extractTargetRef(Node element) {
return element.getAttributes().getNamedItem("targetRef").getNodeValue();
}
/**
* Returns the value of the id attribute for the given {@link Node}.
* @param element
* @return
*/
private static String extractID(Node element) {
return element.getAttributes().getNamedItem("id").getNodeValue();
}
/**
* Parses an {@link BPMNEventType} from the given {@link Node}.
* @param element
* @return
*/
private static BPMNEventType extractIntermediateEventType(Node element) {
//TODO: Implement other intermediate event type
if(!getChildNodesByNodeName(element, "messageEventDefinition").isEmpty()){
return BPMNEventType.Message;
} else if(!getChildNodesByNodeName(element, "timerEventDefinition").isEmpty()){
return BPMNEventType.Timer;
}
return null;
}
/**
* Parses a list of {@link MonitoringPoint}s for the given {@link Node}.
* @param element
* @return
*/
private static List<MonitoringPoint> extractMonitoringPoints(Node element) {
List<MonitoringPoint> monitoringPoints = new ArrayList<MonitoringPoint>();
ArrayList<Node> extensionElementNodes = getChildNodesByNodeName(element, "extensionElements");
assert(extensionElementNodes.size() < 2);
if(extensionElementNodes.size() == 0){
return monitoringPoints;
}
Node extensionElementNode = extensionElementNodes.get(0);
ArrayList<Node> monitoringPointNodes = getChildNodesByNodeName(extensionElementNode, "sushi:transition");
for(Node actualTransitionNode : monitoringPointNodes){
//TODO: Bei Einlesen des Types Groß- und Kleinschreibung abfangen
String monitoringTypeString = actualTransitionNode.getAttributes().getNamedItem("type").getNodeValue();
for(MonitoringPointStateTransition actualMonitoringPointType : MonitoringPointStateTransition.values()){
if(monitoringTypeString.equals(actualMonitoringPointType.toString())){
SushiEventType eventType = SushiEventType.findByTypeName(actualTransitionNode.getAttributes().getNamedItem("regularExpression").getNodeValue());
monitoringPoints.add(new MonitoringPoint(eventType, actualMonitoringPointType, ""));
}
}
}
return monitoringPoints;
}
/**
* Creates a successor and predecessor relationship for the given {@link AbstractBPMNElement} based on the {@link BPMNSequenceFlow}s.
* @param process
* @param element
* @param childNodes
*/
private static void linkActualElement(BPMNProcess process, AbstractBPMNElement element, NodeList childNodes) {
for (int i = 0; i < childNodes.getLength(); i++) {
Node actualNode = childNodes.item(i);
if (actualNode.getNodeType() == 1) {
if (actualNode.getNodeName().equals("incoming")) {
BPMNSequenceFlow sequenceFlow = (BPMNSequenceFlow) process.getBPMNElementById(actualNode.getTextContent());
AbstractBPMNElement.connectElements(process.getBPMNElementById(sequenceFlow.getSourceRef()), element);
} else if (actualNode.getNodeName().equals("outgoing")) {
BPMNSequenceFlow sequenceFlow = (BPMNSequenceFlow) process.getBPMNElementById(actualNode.getTextContent());
AbstractBPMNElement.connectElements(element, process.getBPMNElementById(sequenceFlow.getTargetRef()));
}
}
if (actualNode.getNodeName().equals("subProcess")) {
linkProcessElements(process, actualNode.getChildNodes());
}
}
}
/**
* Creates a successor and predecessor relationship between the parsed {@link AbstractBPMNElement}s based on the {@link BPMNSequenceFlow}s.
* @param process
* @param processElementNodes
*/
private static void linkProcessElements(BPMNProcess process, NodeList processElementNodes) {
for (int i = 0; i < processElementNodes.getLength(); i++) {
Node actualNode = processElementNodes.item(i);
if (actualNode.getNodeType() == 1 && VALID_BPMN_XML_ELEMENTS.contains(actualNode.getNodeName())) {
if (actualNode.hasChildNodes()) {
AbstractBPMNElement element = process.getBPMNElementById(actualNode.getAttributes().getNamedItem("id").getNodeValue());
if(element != null){
linkActualElement(process, element, actualNode.getChildNodes());
if(element instanceof BPMNBoundaryEvent){
attachBoundaryEvent(process, (BPMNBoundaryEvent) element, actualNode);
} else if(element instanceof BPMNSubProcess){
linkProcessElements((BPMNSubProcess) element, actualNode.getChildNodes());
}
}
}
}
}
}
/**
* Attaches a boundary event to the given {@link Node}.
* @param process
* @param boundaryEvent
* @param actualNode
*/
private static void attachBoundaryEvent(BPMNProcess process, BPMNBoundaryEvent boundaryEvent, Node actualNode) {
String attachedToElementID = actualNode.getAttributes().getNamedItem("attachedToRef").getNodeValue();
AbstractBPMNElement attachedToElement = process.getBPMNElementById(attachedToElementID);
boundaryEvent.setAttachedToElement(attachedToElement);
AbstractBPMNElement.connectElements(attachedToElement, boundaryEvent);
if(attachedToElement instanceof AttachableElement){
AttachableElement attachedElement = (AttachableElement) attachedToElement;
attachedElement.setAttachedIntermediateEvent(boundaryEvent);
}
}
/**
* Returns all child nodes for a given node with the given name, if any.
* @param element
* @param nodeName
* @return
*/
private static ArrayList<Node> getChildNodesByNodeName(Node element, String nodeName){
ArrayList<Node> resultList = new ArrayList<Node>();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node actualChildNode = childNodes.item(i);
if(actualChildNode.getNodeType() == 1 && actualChildNode.getNodeName().equals(nodeName)){
resultList.add(actualChildNode);
}
}
return resultList;
}
}