package org.omg.bpmn.miwg.xpath.util; import java.util.LinkedList; import java.util.List; import java.util.Stack; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.custommonkey.xmlunit.Difference; import org.omg.bpmn.miwg.api.AnalysisJob; import org.omg.bpmn.miwg.api.AnalysisOutput; import org.omg.bpmn.miwg.api.output.dom.finding.FindingAssertionEntry; import org.omg.bpmn.miwg.api.output.dom.ok.NodePopEntry; import org.omg.bpmn.miwg.api.output.dom.ok.NodePushEntry; import org.omg.bpmn.miwg.api.output.dom.ok.OKAssertionEntry; import org.omg.bpmn.miwg.xpath.pluggableAssertions.Assertion; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; public abstract class AbstractXpathCheck extends AbstractCheck implements DOMCheck { private XPath xpathTest; private Node currentNode; private Stack<Node> nodeStack; private Stack<Node> currentNodeStack; private Document referenceDocument; protected AnalysisOutput out; protected Stack<Node> __getNodeStack() { return nodeStack; } public boolean isApplicable(String testResultName) { String checkName = getName(); return testResultName.startsWith(checkName); } public AbstractXpathCheck() { super(); xpathTest = null; nodeStack = null; currentNodeStack = null; } private void normalizeNames(Document document) throws XPathExpressionException { Object o = xpathTest.evaluate("//bpmn:*[@name]", document, XPathConstants.NODESET); NodeList nl = (NodeList) o; for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); Attr attr = (Attr) n.getAttributes().getNamedItem("name"); String s = attr.getValue(); // out.println(">>> " + s); s = WhitespaceUtil.normalizeWhitespace(s); while (s.contains(" ")) s = s.replaceAll(" ", " "); s = s.trim(); // out.println(">>>>>" + s); attr.setTextContent(s); } Object o2 = xpathTest.evaluate("//child::text()", head(), XPathConstants.NODESET); NodeList nl2 = (NodeList) o2; for (int i = 0; i < nl2.getLength(); i++) { Node n = nl2.item(i); Text text = (Text) n; text.setTextContent(text.getTextContent().trim()); } } protected Node pop() { NodePopEntry e = new NodePopEntry(callingMethod(), getNodeIDNoNull(nodeStack.peek()), XpathCheckContext.createTestContext(this)); out.pop(e); Node n = nodeStack.pop(); currentNode = currentNodeStack.pop(); return n; } protected String getNodeIDNoNull(Node n) { if (n == null) return ""; String s = getAttribute(n, "id", false); if (s == null) return ""; else return s; } protected void push(Node n) { NodePushEntry e = new NodePushEntry(callingMethod(), getNodeIDNoNull(n), XpathCheckContext.createTestContext(this)); out.push(e); nodeStack.push(n); currentNodeStack.push(currentNode); } protected Node head() { if (nodeStack.isEmpty()) return null; return nodeStack.lastElement(); } public void ok(String message) { out.ok(new OKAssertionEntry(callingMethod(), message, XpathCheckContext.createTestContext(this))); } public void okTop(String message) { out.ok(new OKAssertionEntry(callingMethodTop(), message, XpathCheckContext.createTestContext(this))); } public void finding(String parameter, String message) { out.finding(new FindingAssertionEntry(callingMethod(), message, parameter)); } /** * Logs a finding and derives the name of the assertion from the top method * name. */ public void findingTop(String parameter, String message) { out.finding(new FindingAssertionEntry(callingMethodTop(), message, parameter)); } private void setCurrentNode(Node n) throws Throwable { currentNode = n; triggerAutoChecks(); } private String callingMethod() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); String lastName = null; for (StackTraceElement e : stacktrace) { String methodName = e.getMethodName(); if (methodName.startsWith("navigate") || methodName.startsWith("select") || methodName.startsWith("check")) { lastName = methodName; } } if (lastName == null) return stacktrace[3].getMethodName(); else return lastName; } private String callingMethodTop() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); for (StackTraceElement e : stacktrace) { String methodName = e.getMethodName(); if (methodName.startsWith("navigate") || methodName.startsWith("select") || methodName.startsWith("check")) { return methodName; } } return stacktrace[0].getMethodName(); } private String getAttribute(String name) { return getAttribute(head(), name); } private String getAttribute(Node n, String name) { return getAttribute(n, name, true); } private String getAttribute(Node n, String name, boolean throwFinding) { if (n == null && throwFinding) { finding("", "Node is null"); return null; } Node namedItem = n.getAttributes().getNamedItem(name); if (namedItem == null) { if (throwFinding) { finding(name, "Cannot find attribute"); } return null; } return namedItem.getNodeValue(); } private Node findNode(String expr) throws Throwable { return findNode(head(), expr); } private Node findNode(Node base, String expr) throws XPathExpressionException { Object o = xpathTest.evaluate(expr, base, XPathConstants.NODE); if (o == null) return null; else { Node n = (Node) o; return n; } } protected String getCurrentNodeID() { return getAttribute(currentNode, "id"); } protected String getStackID() { return getAttribute(head(), "id"); } protected String getPath(Node node) { Node c = node; String path = ""; while (c != null) { String nodeName = c.getNodeName(); Node attr = null; if (c.hasAttributes()) { attr = c.getAttributes().getNamedItem("id"); } if (attr != null) { nodeName = String.format("%s[@id='%s']", nodeName, attr); } path = nodeName + "/" + path; c = c.getParentNode(); } return path; } private List<Node> findNodes(Node base, String expr) throws XPathExpressionException { Object o = xpathTest.evaluate(expr, base, XPathConstants.NODESET); if (o == null) return null; else { List<Node> l = new LinkedList<Node>(); NodeList nl = (NodeList) o; for (int i = 0; i < nl.getLength(); i++) l.add(nl.item(i)); return l; } } /*** * This works only if the gateway is on the stack */ public void navigateGatewaySequenceFlowStack(String sequenceFlowName) throws Throwable { navigateReferenceX("bpmn:outgoing", "//bpmn:sequenceFlow[@id='%s']", ".", String.format("self::node()[@name='%s']", sequenceFlowName)); } public void navigateGatewaySequenceFlow(String sequenceFlowName) throws Throwable { navigateReferenceX(currentNode, "bpmn:outgoing", "//bpmn:sequenceFlow[@id='%s']", ".", String.format("self::node()[@name='%s']", sequenceFlowName)); } public void navigateGatewaySequenceFlowCurrentNode(String sequenceFlowName) throws Throwable { navigateReferenceX(currentNode, "bpmn:outgoing", "//bpmn:sequenceFlow[@id='%s']", ".", String.format("self::node()[@name='%s']", sequenceFlowName)); } private void navigateReferenceX(String referenceXpath, String targetXpath, String targetXpathParameter, String targetCheckXpath) throws Throwable { navigateReferenceX(head(), referenceXpath, targetXpath, targetXpathParameter, targetCheckXpath); } private void navigateReferenceX(Node baseNode, String referenceXpath, String targetXpath, String targetXpathParameter, String targetCheckXpath) throws Throwable { if (baseNode == null) { finding("", "Parent failed"); return; } List<Node> nodes = findNodes(baseNode, referenceXpath); if (nodes.size() == 0) { finding(referenceXpath, "No reference nodes found"); return; } Node foundNode = null; for (Node n : nodes) { Node evaluatedTargetXpathParameterNode = findNode(n, targetXpathParameter); if (evaluatedTargetXpathParameterNode == null) { finding(targetXpathParameter, "Target parameter cannot be evaluated"); return; } String evaluatedTargetXpathParameter = evaluatedTargetXpathParameterNode .getTextContent(); String evaluatedXpath = String.format(targetXpath, evaluatedTargetXpathParameter); Node fn = findNode(n, evaluatedXpath); if (fn == null) { finding(evaluatedXpath, "Target node not found"); return; } Node checkedNode = findNode(fn, targetCheckXpath); if (checkedNode != null) { foundNode = fn; } } if (foundNode != null) { String message = String .format("Reference: %s; Target: %s; Target parameter %s; Target Check: %s", referenceXpath, targetXpath, targetXpathParameter, targetCheckXpath); ok(message); setCurrentNode(foundNode); } else { finding(targetCheckXpath, "Target check failed"); return; } } private Node navigateElementX(String expr) throws Throwable { return navigateElementX(expr, null); } public Node navigateElement(String type, String attrKey, String attrValue) throws Throwable { String xpath = String.format("%s[@%s='%s']", type, attrKey, attrValue); // String xpath = String.format("%s", type); Node n = navigateElementX(xpath); return n; } public Node navigateElement(String type, String name) throws Throwable { if (name != null) { String xpath = String.format("%s[@name='%s']", type, name); Node n = navigateElementX(xpath); return n; } else { String xpath = String.format("%s[not(@name)]", type); Node n = navigateElementX(xpath); return n; } } public Node navigateFollowingElement(String type, String name) throws Throwable { return navigateFollowingElement(currentNode, type, name, null, new Assertion[] {}); } public Node navigateFollowingElement(String type, String name, String sequenceFlowName) throws Throwable { return navigateFollowingElement(currentNode, type, name, sequenceFlowName, new Assertion[] {}); } public Node navigateFollowingElement(String type, String name, String sequenceFlowName, Assertion[] assertions) throws Throwable { return navigateFollowingElement(currentNode, type, name, sequenceFlowName, assertions); } public Node navigateSequenceFlow(String type, String name) throws Throwable { return navigateSequenceFlow(currentNode, type, name); } public Node navigateLane(String name) throws Throwable { String xPath = String .format("bpmn:laneSet/bpmn:lane[@name='%s']", name); return navigateElementX(xPath); } public Node navigateDefinitions() throws Throwable { return navigateElementX("/bpmn:definitions"); } protected Node navigateSequenceFlow(Node node, String type, String name) throws Throwable { String nameCondition; if (name == null) { nameCondition = ""; } else { nameCondition = String.format("@name='%s' and ", name); } String targetId = getAttribute(node, "targetRef"); String xpathTarget = String.format("%s[%s@id='%s']", type, nameCondition, targetId); xpathTarget = String.format("%s[@id='%s']", type, targetId); Node n = findNode(xpathTarget); if (n == null) { finding(String.format("%s[@name='%s']", type, name), "No outgoing reference found"); return null; } ok(xpathTarget); setCurrentNode(n); return n; } protected Node navigateFollowingElement(Node node, String type, String name, String sequenceFlowName, Assertion[] assertions) throws Throwable { if (node == null) { finding(null, "The base node is null"); return null; } if (head() == null) { finding(null, "Parent failed"); return null; } String sourceID = getCurrentNodeID(); String xpathSequenceFlow; if (sequenceFlowName == null) { xpathSequenceFlow = String.format( "bpmn:sequenceFlow[@sourceRef='%s']", sourceID); } else { xpathSequenceFlow = String.format( "bpmn:sequenceFlow[@sourceRef='%s' and @name='%s']", sourceID, sequenceFlowName); } List<Node> sequenceFlowNodes = findNodes(head(), xpathSequenceFlow); for (Node sequenceFlowNode : sequenceFlowNodes) { String sequenceFlowID = getNodeIDNoNull(sequenceFlowNode); String nameCondition; if (name == null) { nameCondition = "not(@name) and "; } else if (name.equals("")) { nameCondition = ""; } else { nameCondition = String.format("@name='%s' and ", name); } String targetId = getAttribute(sequenceFlowNode, "targetRef"); String xpathTarget = String.format("%s[%s@id='%s']", type, nameCondition, targetId); Node targetNode = findNode(xpathTarget); if (targetNode != null) { ok(xpathTarget); /** * We have found the correct sequence flow and the target * element. Now we check whether there is a corresponding * incoming and outgoing element (this is necessary as agreed in * issue #100). */ String xpathOutgoing = String.format( "bpmn:outgoing[text()='%s']", sequenceFlowID); Node outgoingNode = findNode(node, xpathOutgoing); if (outgoingNode == null) { finding(xpathOutgoing, "There is no corresponding outgoing node for the sequence flow"); } String xpathIncoming = String.format( "bpmn:incoming[text()='%s']", sequenceFlowID); Node incomingNode = findNode(targetNode, xpathIncoming); if (incomingNode == null) { finding(xpathIncoming, "There is no corresponding incoming node for the sequence flow"); } if (assertions != null) { for (Assertion assertion : assertions) { assertion.check(sequenceFlowNode, this, out); } } setCurrentNode(targetNode); return targetNode; } } finding(String.format("%s[@name='%s']", type, name), "No outgoing reference found"); return null; } private Node navigateElementX(String expr, String param) throws Throwable { if (head() == null) { finding(expr, "Parent failed"); return null; } Node n = findNode(expr); if (n == null) { finding(expr, "No node found"); return null; } ok(expr); setCurrentNode(n); return n; } public void selectCollaboration() throws Throwable { selectElementX("bpmn:collaboration"); } public void selectPool(String name) throws Throwable { String xpath = String.format("bpmn:participant[@name='%s']", name); selectElementX(xpath); } public void selectReferencedProcess() throws Throwable { String ref = getAttribute("processRef"); if (ref == null) { push(null); return; } String xpath = String.format("//bpmn:process[@id='%s']", ref); selectElementX(xpath); } public void navigateBoundaryEvent(String name) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } if (head() == null) { finding(null, "Parent failed"); return; } String currentElementID = getCurrentNodeID(); String nameCondition; if (name == null) { nameCondition = ""; } else { nameCondition = String.format("@name='%s' and ", name); } String xpathBoundaryElement = String.format( "bpmn:boundaryEvent[%s@attachedToRef='%s']", nameCondition, currentElementID); Node n = findNode(xpathBoundaryElement); if (n == null) { finding(xpathBoundaryElement, "Cannot find boundary element"); return; } ok(String.format("Boundary element '%s' found", name)); setCurrentNode(n); } public void navigateBookmarkedElement(Node node) throws Throwable { String path = getPath(node); ok(path); setCurrentNode(node); } public Node selectFollowingElement(String type, String name) throws Throwable { if (head() == null) { finding(String.format("Type: %s, name: %s", type, name), "Parent failed"); push(null); return null; } Node n = navigateFollowingElement(type, name); push(n); return n; } public void selectElement(String type, String name) throws Throwable { String xpath = String.format("%s[@name='%s']", type, name); selectElementX(xpath); } public Node navigateChildElement(String type, String name) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return null; } String xpath; if (name == null) { xpath = String.format("%s[(not(@name) or @name='')]", type, name); // "string-length(@attr)=0"; } else { xpath = String.format("%s[@name='%s']", type, name); } Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "No node found"); return null; } ok(xpath); setCurrentNode(n); return n; } public void checkChildElementValue(String type, String name, String expectedValue) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath; if (name == null) { xpath = String.format("%s[(not(@name) or @name='')]", type, name); // "string-length(@attr)=0"; } else { xpath = String.format("%s[@name='%s']", type, name); } Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "No node found"); return; } String actualValue = n.getTextContent(); if (!expectedValue.equals(actualValue)) { finding(xpath, String.format( "The element has the expected value '%s', but the actual value is '%s'", expectedValue, actualValue)); return; } ok(xpath); } private void triggerAutoChecks() throws Throwable { if (currentNode != null) { if (referenceDocument != null) { // These auto checks only work when there is a reference // document checkAutoExtensionElements(); checkAutoNonBPMNAttributes(); } } } public void selectElementNoID(String type) throws Throwable { String xpath = String.format("%s", type); selectElementX(xpath); } private void selectElementX(String expr) throws Throwable { selectElementX(expr, null); } private void selectElementX(String expr, String param) throws Throwable { if (head() == null) { finding(expr, "Parent failed"); push(null); return; } Node n = findNode(expr); if (n == null) { finding(expr, "No node found"); push(null); return; } ok(expr); push(n); setCurrentNode(n); } public Node selectCallActivityProcess() throws Throwable { if (head() == null) { finding(null, "Parent failed"); push(null); return null; } String calledId = getAttribute(currentNode, "calledElement"); String xpath = String.format("//bpmn:process[@id='%s']", calledId); Node n = findNode(xpath); if (n == null) { finding(xpath, "Process node not found"); push(null); return null; } ok(xpath); push(n); setCurrentNode(n); return n; } public void selectFirstProcess() throws Throwable { selectProcessX("/bpmn:definitions/bpmn:process"); } private void selectProcessX(String xpath) throws Throwable { if (head() == null) { finding(xpath, "Parent failed"); push(null); return; } Object o = findNode(xpath); if (o == null) { finding(xpath, "Process node not found"); push(null); return; } if (!(o instanceof Node)) { finding(xpath, "Wrong type (" + Node.class.getName() + ")"); push(null); return; } Node n = (Node) o; ok(xpath); setCurrentNode(n); push(n); } public void selectProcessByParticipant(String participant) throws Throwable { String xpath = String .format("//bpmn:process[@id=//bpmn:participant[@name='%s']/@processRef]", participant); selectProcessX(xpath); } public void selectProcessByLane(String lane) throws Throwable { String xpath = String.format( "//bpmn:process[bpmn:laneSet/bpmn:lane[@name='%s']]", lane); selectProcessX(xpath); } public void selectProcessOfCallActivity() throws Throwable { if (head() == null) { finding("selectProcessofCallActivity", "Parent failed"); push(null); return; } String processID = getAttribute("calledElement"); if (processID == null) { finding("selectProcessofCallActivity", "Cannot retreive process id"); push(null); return; } String xpath = "//bpmn:process[@id='" + processID + "']"; Node n = findNode(xpath); if (n == null) { finding(xpath, "Cannot find process with id " + processID); push(null); return; } ok(xpath); setCurrentNode(n); push(n); } public void checkDefaultSequenceFlow() throws Throwable { if (currentNode == null) { finding("", "No current node"); return; } if (!currentNode.getLocalName().equals("sequenceFlow")) { finding(currentNode.getLocalName(), "Current node is not a sequenceFlow"); return; } String currentId = getAttribute(currentNode, "id"); String sourceRef = getAttribute(currentNode, "sourceRef"); String xpath = String.format("//bpmn:*[@id='%s']", sourceRef); Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find source gateway"); return; } String value = getAttribute(n, "default"); if (!currentId.equals(value)) { finding(null, "Not a default sequence flow"); return; } ok("Default Sequence Flow"); } public void checkAttributeExists(String attribute) throws Throwable { if (currentNode == null) { finding("", "No current node"); return; } String s = getAttribute(attribute); if (s == null) { // Finding is thrown by getAttribute return; } ok("Attribute " + attribute + "exists"); } protected void checkAttributeValue(String attribute, boolean value, boolean defaultValue) throws Throwable { checkAttributeValue(attribute, Boolean.toString(value), Boolean.toString(defaultValue)); } protected void checkAttributeValue(String attribute, String value) throws Throwable { if (currentNode == null) { finding("", "No Current node"); return; } Node attr = currentNode.getAttributes().getNamedItem(attribute); if (attr == null) { finding("", String.format("Attribute %s is not existing.", attribute)); return; } String s = attr.getTextContent(); if (!s.equals(value)) { finding(attribute, String.format( "Attribute %s does not have the expected value '%s'", attribute, value)); return; } ok("Attribute " + attribute + "=" + value); } protected String checkAttributeValue(String attribute) throws Throwable { if (currentNode == null) { finding("", "Null node"); return null; } Node attr = currentNode.getAttributes().getNamedItem(attribute); if (attr == null) { finding("", String.format("Attribute %s is not existing.", attribute)); return null; } String value = attr.getTextContent(); ok("Attribute " + attribute + "=" + value); return value; } protected void checkAttributeValue(Node n, String attribute, String value) throws Throwable { if (n == null) { finding("", "Null node"); return; } Node attr = n.getAttributes().getNamedItem(attribute); if (attr == null) { finding("", String.format("Attribute %s is not existing.", attribute)); return; } String s = attr.getTextContent(); if (!s.equals(value)) { finding(attribute, String.format( "Attribute %s does not have the expected value '%s'", attribute, value)); return; } ok("Attribute " + attribute + "=" + value); } protected void checkAttributeValue(String attribute, String value, String defaultValue) throws Throwable { if (currentNode == null) { finding("", "No current node"); return; } Node attr = currentNode.getAttributes().getNamedItem(attribute); if (attr == null) { if (value.equals(defaultValue)) { ok(String .format("Attribute %s is not existing but the expected value (%s) equals the default value (%s)", attribute, value, defaultValue)); return; } else { finding("", String.format( "Attribute %s is not existing and the expected value (%s) is not equal to the default value (%s)", attribute, value, defaultValue)); return; } } String s = attr.getTextContent(); if (!s.equals(value)) { finding(attribute, String.format( "Attribute %s does not have the expected value '%s'", attribute, value)); return; } ok("Attribute " + attribute + "=" + value); } protected void checkCancelActivity(boolean value) throws Throwable { checkAttributeValue("cancelActivity", value, true); } protected void checkParallelMultiple(boolean value) throws Throwable { checkAttributeValue("parallelMultiple", value, false); } private String artifactTypeToString(ArtifactType artifactType) { switch (artifactType) { case DataStoreReference: return "dataStoreReference"; case DataObject: return "dataObject"; default: assert false; return null; } } public void checkDataAssociation(ArtifactType artifactType, String artifactName, Direction associationDirection) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } Node elementNode = currentNode; push(elementNode); String data; String setDataRef; String dataAssociationRef; String artifactRef; switch (associationDirection) { case Input: data = "bpmn:ioSpecification/bpmn:dataInput"; setDataRef = "bpmn:ioSpecification/bpmn:inputSet/bpmn:dataInputRefs[text()='%s']"; dataAssociationRef = "bpmn:dataInputAssociation/bpmn:targetRef[text()='%s']"; artifactRef = "../bpmn:sourceRef"; break; case Output: data = "bpmn:ioSpecification/bpmn:dataOutput"; setDataRef = "bpmn:ioSpecification/bpmn:outputSet/bpmn:dataOutputRefs[text()='%s']"; dataAssociationRef = "bpmn:dataOutputAssociation/bpmn:sourceRef[text()='%s']"; artifactRef = "../bpmn:targetRef"; break; default: data = null; setDataRef = null; dataAssociationRef = null; artifactRef = null; assert false; } for (Node n : findNodes(elementNode, data)) { String id = getAttribute(n, "id"); { String setDataRef2 = String.format(setDataRef, id); Node dataRefNode = findNode(elementNode, setDataRef2); if (dataRefNode == null) { finding(setDataRef2, "Cannot find node"); pop(); return; } } { String dataAssociationRef2 = String.format(dataAssociationRef, id); Node dataAssociationRefNode = findNode(elementNode, dataAssociationRef2); if (dataAssociationRefNode == null) { finding(dataAssociationRef2, "Cannot find node"); pop(); return; } else { Node artifactRefNode = findNode(dataAssociationRefNode, artifactRef); if (artifactRefNode == null) { finding(artifactRef, "Cannot find node"); pop(); return; } String artifactID = artifactRefNode.getTextContent(); String artifact; switch (artifactType) { case DataStoreReference: artifact = "../bpmn:dataStoreReference[@id='%s']"; break; case DataObject: artifact = "../bpmn:dataObjectReference[@id='%s']"; break; default: artifact = null; assert false; } String artifact2 = String.format(artifact, artifactID); Node artifactNode = findNode(elementNode, artifact2); if (artifactNode == null) { finding(artifact2, "Cannot find artifact node"); pop(); return; } if (getAttribute(artifactNode, "name").equals(artifactName)) { ok(String.format("Association reference %s '%s' found", artifactTypeToString(artifactType), artifactName)); pop(); return; } } } } finding(null, String.format( "Cannot find the associated artifact %s '%s'", artifactTypeToString(artifactType), artifactName)); pop(); } public void checkTerminateEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:terminateEventDefinition | bpmn:eventDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find terminate event definition"); return; } else { ok("Terminate event definition"); return; } } public void checkSignalEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:signalEventDefinition | bpmn:signalEventDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find signal event definition"); return; } else { ok("Signal event definition"); return; } } public void checkMessageEvent() throws Throwable { checkMessageEvent(false, null); } public void checkMessageEvent(boolean followMessageRef) throws Throwable { checkMessageEvent(followMessageRef, null); } public void checkMessageEvent(boolean followMessageRef, String expectedMessageName) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:messageEventDefinition | bpmn:messageEventDefinitionRef"; Node messageEventDefinitionNode = findNode(currentNode, xpath); if (messageEventDefinitionNode == null) { finding(xpath, "Cannot find message event definition"); return; } if (followMessageRef) { String messageID = getAttribute(messageEventDefinitionNode, "messageRef", false); if (messageID == null) { finding(null, "The messageEventDefinition element has no messageRef attribute"); return; } String messageXpath = String.format("//bpmn:message[@id='%s']", messageID); Node messageNode = findNode(messageXpath); if (messageNode == null) { finding(messageXpath, String.format( "Cannot find the referenced message with the id %s", messageID)); return; } if (expectedMessageName != null) { String actualMessageName = getAttribute(messageNode, "name", false); if (!expectedMessageName.equals(actualMessageName)) { finding(null, String.format( "The name of the referenced message should be %s, but it is %s", expectedMessageName, actualMessageName)); return; } } } ok("Message event definition"); return; } public void checkTimerEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:timerEventDefinition | bpmn:eventDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find timer event definition"); return; } else { ok("Timer event definition"); return; } } public void checkXORMarkersForProcess(boolean hasMarker) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = ".//bpmn:exclusiveGateway"; List<Node> gatewayNodes = findNodes(currentNode, xpath); for (Node gatewayNode : gatewayNodes) { String id = getAttribute(gatewayNode, "id"); String xpath2 = String.format( "//bpmndi:BPMNShape[@bpmnElement='%s']", id); Node n = findNode(currentNode, xpath2); if (n == null) { finding(xpath, String.format( "There is no BPMNShape element for the BPMN element with the id '%s'.", id)); return; } else { String value = getAttribute(n, "isMarkerVisible", false); if (value == null) { if (hasMarker) { finding(null, String.format( "There is no isMarkerVisible attribute in the BPMNShape element for the BPMN element with the id '%s' although %s is expected.", id, Boolean.toString(true))); return; } } else { if (!value.equals(Boolean.toString(hasMarker))) { finding(null, String.format( "The XOR marker for the BPMN element with the id '%s' is %s although %s is expected.", id, value, Boolean.toString(hasMarker))); return; } } } ok(String.format("All XOR markers have the expected value '%s'.", Boolean.toString(hasMarker))); } } public void checkEscalationEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:escalationEventDefinition | bpmn:escalationDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find escalation event definition"); return; } else { ok("Escalation event definition"); return; } } public void checkLinkEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:linkEventDefinition | bpmn:linkDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find link event definition"); return; } else { ok("Link event definition"); return; } } protected Node findCorrespondingReferenceElement(Node testNode) throws XPathExpressionException { if (getReferenceDocument() == null) { findingTop(null, "The reference has not been specified"); return null; } String id = getCurrentNodeID(); String xpath = "//bpmn:*[@id='" + id + "']"; Node refNode = (Node) getXpathTest().evaluate(xpath, getReferenceDocument(), XPathConstants.NODE); if (refNode == null) { findingTop(xpath, "Cannot find corresponding element in reference DOM"); return null; } return refNode; } /** * This method checks whether all extension elements from the reference * exist in the test file as well. * * @throws Throwable */ public void checkAutoExtensionElements() throws Throwable { if (currentNode == null) { findingTop(null, "Current node is null"); return; } Node refNode = findCorrespondingReferenceElement(currentNode); if (refNode == null) { // Findings are generated by above method. return; } String xpath = "bpmn:extensionElements"; Node refEE = (Node) getXpathTest().evaluate(xpath, refNode, XPathConstants.NODE); if (refEE == null) { /* * The corresponding reference element contains no extension * element. This is ok. */ // findingTop(xpath, // "Cannot find corresponding extension element in reference DOM"); return; } Node testEE = (Node) getXpathTest().evaluate(xpath, currentNode, XPathConstants.NODE); if (testEE == null) { findingTop( xpath, "Cannot find extension element although the corresponding reference element contains an extension element"); return; } List<Difference> differences = DOMUtil.checkSubtreeForDifferences( testEE, refEE); if (differences.isEmpty()) { okTop("Extension elements are similar to the reference"); return; } else { findingTop(null, "Extension Elements are not similar to the reference"); push(testEE); for (Difference difference : differences) { findingTop(null, difference.getId() + ": " + difference.toString()); } pop(); } } public void checkAutoNonBPMNAttributes() throws Throwable { Node testNode = currentNode; if (currentNode == null) { findingTop(null, "No current node"); } Node refNode = findCorrespondingReferenceElement(currentNode); if (refNode == null) return; NamedNodeMap refAttrs = refNode.getAttributes(); NamedNodeMap testAttrs = testNode.getAttributes(); boolean hasNonBPMNAttributes = false; for (int i = 0; i < refAttrs.getLength(); i++) { Node refAttr = refAttrs.item(i); if (refAttr.getNamespaceURI() != null && !refAttr.getNamespaceURI().equals( NameSpaceContexts.BPMN_MODEL_NS_URI)) { hasNonBPMNAttributes = true; Node testAttr = testAttrs.getNamedItemNS( refAttr.getNamespaceURI(), refAttr.getLocalName()); if (testAttr == null) { findingTop(null, "Cannot find attribute '" + refAttr.toString() + "' from reference in test file"); return; } if (!testAttr.getNodeValue().equals(refAttr.getNodeValue())) { findingTop( null, "The attribute '" + refAttr.toString() + "' has the value '" + testAttr.getNodeValue() + "'. In the reference the value is '" + refAttr.getNodeValue() + "'."); return; } } } if (hasNonBPMNAttributes) { okTop("All non-BPMN attributes from reference exist in the test document as well."); } else { /* There are no non-BPMN attributes. */ } } public void checkErrorEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:errorEventDefinition | bpmn:errorDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find error event definition"); return; } else { ok("Error event definition"); return; } } public void checkName(String nameExpected) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String nameActual = getAttribute(currentNode, "name", false); if (nameActual == null) { finding("", "No name attribute"); return; } else if (nameExpected.equals(nameActual)) { ok(String.format("Element name attribute: %s", nameExpected)); return; } else { finding("", String.format("Wrong name (expected: %s, actual: %s", nameExpected, nameActual)); return; } } public void checkGlobalTask(boolean globalTaskShouldExist) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String calledElement = getAttribute(currentNode, "calledElement", false); if (globalTaskShouldExist) { if (calledElement == null) { finding(null, "Attribute 'calledElement' does not exist"); return; } String xpath = String.format("//bpmn:globalUserTask[@id='%s']", calledElement); Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find global Task"); return; } else { ok("Global Task is referenced"); return; } } else { if (calledElement != null) { finding(null, "Attribute 'calledElement' exists although the test expects that this element does not exist"); return; } else { ok("Attibute 'calledElement' does not exist (as expected)"); return; } } } /** * Currently, the text annotation may be placed anywhere in the DOM. */ public void checkTextAssociation(String text) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } if (head() == null) { finding(null, "Parent failed"); return; } String currentID = getCurrentNodeID(); if (currentID == null) { return; } String xpath = String .format("//bpmn:textAnnotation[@id=../bpmn:association[@sourceRef='%s']/@targetRef]/bpmn:text", currentID); List<Node> nl = findNodes(head(), xpath); for (Node candidate : nl) { String value = candidate.getTextContent(); if (value.equals(text)) { ok(String.format("Text annotation found: %s", text)); return; } } finding(xpath, String.format("Text annotation '%s' not found", text)); } public void checkConditionalEvent() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:conditionalEventDefinition | bpmn:conditionalDefinitionRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find conditional event definition"); return; } else { String xpath2 = "bpmn:condition"; Node n2 = findNode(n, xpath2); if (n2 == null) { finding(xpath2, "Cannot find condition"); return; } ok("Conditional event definition"); return; } } public void checkMessageFlow(String name, Direction direction) throws Throwable { checkMessageFlow(name, direction, null, null); } public void checkMessageFlow(String name, Direction direction, String partnerType, String partnerName) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } if (head() == null) { finding(null, "Parent failed"); return; } String dir; switch (direction) { case Input: dir = "targetRef"; break; case Output: dir = "sourceRef"; break; default: assert false; return; } String xpathName; if (name == null || name.equals("")) { xpathName = "(not(@name) or @name='')"; // "string-length(@attr)=0"; } else { xpathName = String.format("@name='%s'", name); } String xpath = String.format("//bpmn:messageFlow[%s and @%s='%s']", xpathName, dir, getCurrentNodeID()); Node n = findNode(xpath); if (n == null) { finding(xpath, "Could not find messageFlow"); return; } if (partnerType != null && partnerName != null) { String partnerDirectionAttribute; switch (direction) { case Input: partnerDirectionAttribute = "sourceRef"; break; case Output: partnerDirectionAttribute = "targetRef"; break; default: assert false; return; } String targetID = getAttribute(n, partnerDirectionAttribute); String xpathTarget = String.format("//%s[@name='%s' and @id='%s']", partnerType, partnerName, targetID); Node targetNode = findNode(xpathTarget); if (targetNode == null) { finding(xpathTarget, "Could not find messageFlow partner"); return; } ok(String.format("message flow '%s' (%s) -> %s", name, direction, xpathTarget)); } else { ok(String.format("message flow '%s' (%s)", name, direction)); } } protected void checkMultiInstance(boolean sequential) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath = "bpmn:multiInstanceLoopCharacteristics"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Loop characteristics not set"); return; } boolean defaultValue = false; String attribute = "isSequential"; Node attr = n.getAttributes().getNamedItem(attribute); if (attr == null) { if (sequential == defaultValue) { ok(String .format("Attribute %s is not existing but the expected value (%s) equals the default value (%s)", attribute, sequential, defaultValue)); return; } else { finding("", String.format( "Attribute %s is not existing and the expected value (%s) is not equal to the default value (%s)", attribute, sequential, defaultValue)); return; } } else { String v = attr.getTextContent(); if (!v.equals(Boolean.toString(sequential))) { finding(null, String.format("%s=%s, should be %s", attribute, v, sequential)); return; } ok(String.format( "Multi instance loop characteristics (sequential=%s)", sequential)); } } public void checkMultiInstanceSequential() throws Throwable { checkMultiInstance(true); } public void checkMultiInstanceParallel() throws Throwable { checkMultiInstance(false); } public void checkEventGatewayExclusive(boolean exclusive) throws Throwable { checkAttributeValue("eventGatewayType", exclusive ? "Exclusive" : "Inclusive", "Exclusive"); } public void checkMessageDefinition() throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String messageID = getAttribute(currentNode, "messageRef"); if (messageID == null) { // Finding is raised by getAttribute return; } String xpath = String.format("//bpmn:message[@id='%s']", messageID); Node n = findNode(xpath); if (n == null) { finding(xpath, "No message definition"); return; } ok("Message definition"); } public void checkOwner(OwnerType ownerType, String potentialOwner) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String xpath; if (ownerType == OwnerType.PotentialOwner) xpath = "bpmn:potentialOwner/bpmn:resourceRef"; else xpath = "bpmn:performer/bpmn:resourceRef"; Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find resource reference"); return; } String ref = n.getTextContent(); String xpath2 = String.format( "/bpmn:definitions/bpmn:resource[@id='%s']", ref); Node n2 = findNode(currentNode, xpath2); if (n2 == null) { finding(xpath, "Cannot find resource"); return; } checkAttributeValue(n2, "name", potentialOwner); } public void checkOperation(String operation) throws Throwable { if (currentNode == null) { finding(null, "Current node is null"); return; } String ref = getAttribute(currentNode, "operationRef"); String xpath = String.format( "/bpmn:definitions/bpmn:interface/bpmn:operation[@id='%s']", ref); Node n = findNode(currentNode, xpath); if (n == null) { finding(xpath, "Cannot find operation"); return; } checkAttributeValue(n, "name", operation); } @Override public void execute(Document actualDocument, Document referenceDocument, AnalysisJob job, AnalysisOutput output) throws Throwable { this.out = output; this.doc = actualDocument; this.referenceDocument = referenceDocument; XPathFactory xpathfactory = XPathFactory.newInstance(); xpathTest = xpathfactory.newXPath(); xpathTest.setNamespaceContext(new NameSpaceContexts()); nodeStack = new Stack<Node>(); currentNodeStack = new Stack<Node>(); push(doc.getDocumentElement()); normalizeNames(doc); if (this.referenceDocument != null) normalizeNames(this.referenceDocument); doExecute(); } protected abstract void doExecute() throws Throwable; public Node getCurrentNode() { return currentNode; } public Document getReferenceDocument() { return referenceDocument; } public XPath getXpathTest() { return xpathTest; } }