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

de.viadee.bpm.vPAV.processing.ElementGraphBuilder Maven / Gradle / Ivy

Go to download

The tool checks Camunda projects for consistency and discovers errors in process-driven applications. Called as a Maven plugin or JUnit test, it discovers esp. inconsistencies of a given BPMN model in the classpath and the sourcecode of an underlying java project, such as a delegate reference to a non-existing java class or a non-existing Spring bean.

There is a newer version: 3.0.8
Show newest version
/**
 * Copyright © 2017, viadee Unternehmensberatung GmbH
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by the viadee Unternehmensberatung GmbH.
 * 4. Neither the name of the viadee Unternehmensberatung GmbH nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY  ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package de.viadee.bpm.vPAV.processing;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.Activity;
import org.camunda.bpm.model.bpmn.instance.BaseElement;
import org.camunda.bpm.model.bpmn.instance.BoundaryEvent;
import org.camunda.bpm.model.bpmn.instance.CallActivity;
import org.camunda.bpm.model.bpmn.instance.EndEvent;
import org.camunda.bpm.model.bpmn.instance.Event;
import org.camunda.bpm.model.bpmn.instance.ExtensionElements;
import org.camunda.bpm.model.bpmn.instance.FlowElement;
import org.camunda.bpm.model.bpmn.instance.Message;
import org.camunda.bpm.model.bpmn.instance.MessageEventDefinition;
import org.camunda.bpm.model.bpmn.instance.ParallelGateway;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.bpmn.instance.StartEvent;
import org.camunda.bpm.model.bpmn.instance.SubProcess;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaIn;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaOut;

import de.viadee.bpm.vPAV.BPMNScanner;
import de.viadee.bpm.vPAV.RuntimeConfig;
import de.viadee.bpm.vPAV.processing.model.data.AnomalyContainer;
import de.viadee.bpm.vPAV.processing.model.data.BpmnElement;
import de.viadee.bpm.vPAV.processing.model.data.ElementChapter;
import de.viadee.bpm.vPAV.processing.model.data.KnownElementFieldType;
import de.viadee.bpm.vPAV.processing.model.data.ProcessVariable;
import de.viadee.bpm.vPAV.processing.model.data.VariableOperation;
import de.viadee.bpm.vPAV.processing.model.graph.Edge;
import de.viadee.bpm.vPAV.processing.model.graph.Graph;
import de.viadee.bpm.vPAV.processing.model.graph.IGraph;
import de.viadee.bpm.vPAV.processing.model.graph.Path;

/**
 * Creates data flow graph based on a bpmn model
 *
 */
public class ElementGraphBuilder {

    private Map elementMap = new HashMap();

    private Map processIdToPathMap;

    private Map decisionRefToPathMap;

    private Map> messageIdToVariables;

    private Map> processIdToVariables;

    private BPMNScanner bpmnScanner;

    public ElementGraphBuilder(BPMNScanner bpmnScanner) {
        this.bpmnScanner = bpmnScanner;
    }

    public ElementGraphBuilder(final Map decisionRefToPathMap,
            final Map processIdToPathMap, final Map> messageIdToVariables,
            final Map> processIdToVariables, BPMNScanner bpmnScanner) {
        this.decisionRefToPathMap = decisionRefToPathMap;
        this.processIdToPathMap = processIdToPathMap;
        this.messageIdToVariables = messageIdToVariables;
        this.processIdToVariables = processIdToVariables;
        this.bpmnScanner = bpmnScanner;
    }

    /**
     * Create data flow graphs for a model
     *
     * @param modelInstance
     *            BpmnModelInstance
     * @param processdefinition
     *            processdefinitions
     * @param calledElementHierarchy
     *            calledElementHierarchy
     * @return graphCollection returns graphCollection
     */
    public Collection createProcessGraph(final BpmnModelInstance modelInstance,
            final String processdefinition, final Collection calledElementHierarchy) {

        final Collection graphCollection = new ArrayList();

        final Collection processes = modelInstance.getModelElementsByType(Process.class);
        for (final Process process : processes) {
            final IGraph graph = new Graph(process.getId());
            final Collection elements = process.getFlowElements();
            final Collection flows = new ArrayList();
            final Collection boundaryEvents = new ArrayList();
            final Collection subProcesses = new ArrayList();
            final Collection callActivities = new ArrayList();

            for (final FlowElement element : elements) {
                if (element instanceof SequenceFlow) {
                    // mention sequence flows
                    final SequenceFlow flow = (SequenceFlow) element;
                    flows.add(flow);
                } else if (element instanceof BoundaryEvent) {
                    // mention boundary events
                    final BoundaryEvent event = (BoundaryEvent) element;
                    boundaryEvents.add(event);
                } else if (element instanceof CallActivity) {
                    // mention call activities
                    final CallActivity callActivity = (CallActivity) element;
                    callActivities.add(callActivity);
                } else if (element instanceof SubProcess) {
                    final SubProcess subprocess = (SubProcess) element;
                    addElementsSubprocess(subProcesses, flows, boundaryEvents, graph, subprocess,
                            processdefinition);
                }
                // initialize element
                final BpmnElement node = new BpmnElement(processdefinition, element);
                // examine process variables and save it with access operation
                final Map variables = new ProcessVariableReader(decisionRefToPathMap,
                        bpmnScanner).getVariablesFromElement(node);
                // examine process variables for element and set it
                node.setProcessVariables(variables);

                // mention element
                elementMap.put(element.getId(), node);
                if (element.getElementType().getBaseType().getBaseType().getTypeName().equals("event")) {
                    // add variables for message event (set by outer class)
                    addProcessVariablesForMessageName(element, node);
                }
                if (element.getElementType().getTypeName().equals("startEvent")) {
                    // add process variables for start event, which set by call startProcessInstanceByKey
                    final String processId = node.getBaseElement().getParentElement().getAttributeValue("id");
                    addProcessVariablesByStartForProcessId(node, processId);

                    graph.addStartNode(node);
                }
                if (element.getElementType().getTypeName().equals("endEvent")) {
                    graph.addEndNode(node);
                }
                // save process elements as a node
                graph.addVertex(node);
            }
            // add edges into the graph
            addEdges(processdefinition, graph, flows, boundaryEvents, subProcesses);

            // resolve call activities and integrate called processes
            for (final CallActivity callActivity : callActivities) {
                integrateCallActivityFlow(processdefinition, modelInstance, callActivity, graph,
                        calledElementHierarchy);
            }

            graphCollection.add(graph);
        }

        return graphCollection;
    }

    /**
     * Add process variables on start event for a specific process id
     *
     * @param node
     * @param processId
     */
    private void addProcessVariablesByStartForProcessId(final BpmnElement node,
            final String processId) {
        if (processIdToVariables != null && processId != null) {
            final Collection outerVariables = processIdToVariables.get(processId);
            // add variables
            if (outerVariables != null) {
                for (final String varName : outerVariables) {
                    node.setProcessVariable(varName,
                            new ProcessVariable(varName, node, ElementChapter.OutstandingVariable,
                                    KnownElementFieldType.Class, null, VariableOperation.WRITE, ""));
                }
            }
        }
    }

    /**
     * Add process variables on event for a specific message name
     *
     * @param element
     *            FlowElement
     * @param node
     *            BpmnElement
     */
    private void addProcessVariablesForMessageName(final FlowElement element,
            final BpmnElement node) {
        if (messageIdToVariables != null) {
            if (element instanceof Event) {
                final Event event = (Event) element;
                final Collection messageEventDefinitions = event
                        .getChildElementsByType(MessageEventDefinition.class);
                if (messageEventDefinitions != null) {
                    for (MessageEventDefinition eventDef : messageEventDefinitions) {
                        if (eventDef != null) {
                            final Message message = eventDef.getMessage();
                            if (message != null) {
                                final String messageName = message.getName();
                                final Collection outerVariables = messageIdToVariables.get(messageName);
                                if (outerVariables != null) {
                                    for (final String varName : outerVariables) {
                                        node.setProcessVariable(varName,
                                                new ProcessVariable(varName, node, ElementChapter.OutstandingVariable,
                                                        KnownElementFieldType.Class, null, VariableOperation.WRITE,
                                                        ""));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public BpmnElement getElement(final String id) {
        return elementMap.get(id);
    }

    /**
     * Create invalid paths for data flow anomalies
     *
     * @param graphCollection
     *            IGraph
     * @return invalidPathMap returns invalidPathMap
     */
    public Map> createInvalidPaths(
            final Collection graphCollection) {
        final Map> invalidPathMap = new HashMap>();

        for (final IGraph g : graphCollection) {
            // add data flow information to graph
            g.setAnomalyInformation(g.getStartNodes().iterator().next());
            // get nodes with data anomalies
            final Map> anomalies = g.getNodesWithAnomalies();

            for (final BpmnElement element : anomalies.keySet()) {
                for (AnomalyContainer anomaly : anomalies.get(element)) {
                    // create paths for data flow anomalies
                    final List paths = g.getAllInvalidPaths(element, anomaly);
                    for (final Path path : paths) {
                        // reverse order for a better readability
                        Collections.reverse(path.getElements());
                    }
                    invalidPathMap.put(anomaly, new ArrayList(paths));
                }
            }
        }

        return invalidPathMap;
    }

    /**
     * Add edges to data flow graph
     *
     * @param processdefinition
     * @param graph
     * @param flows
     * @param boundaryEvents
     * @param subProcesses
     */
    private void addEdges(final String processdefinition, final IGraph graph,
            final Collection flows, final Collection boundaryEvents,
            final Collection subProcesses) {
        for (final SequenceFlow flow : flows) {
            final BpmnElement flowElement = elementMap.get(flow.getId());
            final BpmnElement srcElement = elementMap.get(flow.getSource().getId());
            final BpmnElement destElement = elementMap.get(flow.getTarget().getId());

            graph.addEdge(srcElement, flowElement, 100);
            graph.addEdge(flowElement, destElement, 100);
        }
        for (final BoundaryEvent event : boundaryEvents) {
            final BpmnElement dstElement = elementMap.get(event.getId());
            final Activity source = event.getAttachedTo();
            final BpmnElement srcElement = elementMap.get(source.getId());
            graph.addEdge(srcElement, dstElement, 100);
        }
        for (final SubProcess subProcess : subProcesses) {
            final BpmnElement subprocessElement = elementMap.get(subProcess.getId());
            // integration of a subprocess in data flow graph
            // inner elements will be directly connected into the graph
            final Collection startEvents = subProcess
                    .getChildElementsByType(StartEvent.class);
            final Collection endEvents = subProcess.getChildElementsByType(EndEvent.class);
            if (startEvents != null && startEvents.size() > 0 && endEvents != null
                    && endEvents.size() > 0) {
                final Collection incomingFlows = subProcess.getIncoming();
                for (final SequenceFlow incomingFlow : incomingFlows) {
                    final BpmnElement srcElement = elementMap.get(incomingFlow.getId());
                    for (final StartEvent startEvent : startEvents) {
                        final BpmnElement dstElement = elementMap.get(startEvent.getId());
                        graph.addEdge(srcElement, dstElement, 100);
                        graph.removeEdge(srcElement, subprocessElement);
                    }
                }
                final Collection outgoingFlows = subProcess.getOutgoing();
                for (final EndEvent endEvent : endEvents) {
                    final BpmnElement srcElement = elementMap.get(endEvent.getId());
                    for (final SequenceFlow outgoingFlow : outgoingFlows) {
                        final BpmnElement dstElement = elementMap.get(outgoingFlow.getId());
                        graph.addEdge(srcElement, dstElement, 100);
                        graph.removeEdge(subprocessElement, dstElement);
                    }
                }
            }
        }
    }

    /**
     * Add elements from subprocess to data flow graph
     *
     * @param subProcesses
     * @param flows
     * @param graph
     * @param process
     * @param processdefinitionPath
     * @param cl
     */
    private void addElementsSubprocess(final Collection subProcesses,
            final Collection flows, final Collection events,
            final IGraph graph, final SubProcess process, final String processdefinitionPath) {
        subProcesses.add(process);
        final Collection subElements = process.getFlowElements();
        for (final FlowElement subElement : subElements) {
            if (subElement instanceof SubProcess) {
                final SubProcess subProcess = (SubProcess) subElement;
                addElementsSubprocess(subProcesses, flows, events, graph, subProcess, processdefinitionPath);
            } else if (subElement instanceof SequenceFlow) {
                final SequenceFlow flow = (SequenceFlow) subElement;
                flows.add(flow);
            } else if (subElement instanceof BoundaryEvent) {
                final BoundaryEvent boundaryEvent = (BoundaryEvent) subElement;
                events.add(boundaryEvent);
            }
            // add elements of the sub process as nodes
            final BpmnElement node = new BpmnElement(processdefinitionPath, subElement);
            // determine process variables with operations
            final Map variables = new ProcessVariableReader(decisionRefToPathMap, bpmnScanner)
                    .getVariablesFromElement(node);
            // set process variables for the node
            node.setProcessVariables(variables);
            // mention the element
            elementMap.put(subElement.getId(), node);
            // add element as node
            graph.addVertex(node);
        }
    }

    /**
     * Integrate a called activity into data flow graph
     *
     * @param processdefinition
     * @param modelInstance
     * @param callActivity
     * @param graph
     * @param classLoader
     * @param calledElementHierarchy
     */
    private void integrateCallActivityFlow(final String processdefinition,
            final BpmnModelInstance modelInstance, final CallActivity callActivity, final IGraph graph,
            final Collection calledElementHierarchy) {

        final String calledElement = callActivity.getCalledElement();

        // check call hierarchy to avoid deadlocks
        if (calledElementHierarchy.contains(calledElement)) {
            throw new RuntimeException("call activity hierarchy causes a deadlock (see "
                    + processdefinition + ", " + callActivity.getId() + "). please avoid loops.");
        }
        calledElementHierarchy.add(calledElement);

        // integrate only, if file locations for process ids are known
        if (processIdToPathMap != null && processIdToPathMap.get(calledElement) != null) {

            // 1) read in- and output variables from call activity
            final Collection inVariables = new ArrayList();
            final Collection outVariables = new ArrayList();
            readCallActivityDataInterfaces(callActivity, inVariables, outVariables);

            // 2) add parallel gateways before and after the call activity in the main data flow
            // They are necessary for connecting the sub process with the main flow
            final List parallelGateways = addParallelGatewaysBeforeAndAfterCallActivityInMainDataFlow(
                    modelInstance, callActivity, graph);
            final BpmnElement parallelGateway1 = parallelGateways.get(0);
            final BpmnElement parallelGateway2 = parallelGateways.get(1);

            // get file path of the called process
            final String callActivityPath = processIdToPathMap.get(calledElement);
            if (callActivityPath != null) {
                // 3) load process and transform it into a data flow graph
                final Collection subgraphs = createSubDataFlowsFromCallActivity(
                        RuntimeConfig.getInstance().getClassLoader(),
                        calledElementHierarchy, callActivityPath);

                for (final IGraph subgraph : subgraphs) {
                    // look only on the called process!
                    if (subgraph.getProcessId().equals(calledElement)) {
                        // 4) connect sub data flow with the main data flow
                        connectParallelGatewaysWithSubDataFlow(graph, inVariables, outVariables,
                                parallelGateway1, parallelGateway2, subgraph);
                    }
                }
            }
        }
    }

    /**
     * Add parallel gateways before and after a call activity
     *
     * there are needed to connect the called process with the main flow
     *
     * @param modelInstance
     * @param callActivity
     * @param graph
     * @return parallel gateway elements
     */
    private List addParallelGatewaysBeforeAndAfterCallActivityInMainDataFlow(
            final BpmnModelInstance modelInstance, final CallActivity callActivity, final IGraph graph) {

        final ParallelGateway element1 = modelInstance.newInstance(ParallelGateway.class);
        element1.setAttributeValue("id", "_gw_in", true);

        final ParallelGateway element2 = modelInstance.newInstance(ParallelGateway.class);
        element2.setAttributeValue("id", "_gw_out", true);

        final List elements = new ArrayList();
        final BpmnElement parallelGateway1 = new BpmnElement(null, element1);
        final BpmnElement parallelGateway2 = new BpmnElement(null, element2);
        elements.add(parallelGateway1);
        elements.add(parallelGateway2);

        graph.addVertex(parallelGateway1);
        graph.addVertex(parallelGateway2);

        connectParallelGatewaysWithMainDataFlow(callActivity, graph, parallelGateway1,
                parallelGateway2);

        return elements;
    }

    /**
     * Connect the parallel gateways in the data flow before and after the call activity
     *
     * @param graph
     * @param inVariables
     * @param outVariables
     * @param parallelGateway1
     * @param parallelGateway2
     * @param subgraph
     */
    private void connectParallelGatewaysWithSubDataFlow(final IGraph graph,
            final Collection inVariables, final Collection outVariables,
            final BpmnElement parallelGateway1, final BpmnElement parallelGateway2,
            final IGraph subgraph) {

        // read nodes of the sub data flow
        final Collection vertices = subgraph.getVertices();
        for (final BpmnElement vertex : vertices) {
            // add _ before the element id to avoid name clashes
            final BaseElement baseElement = vertex.getBaseElement();
            baseElement.setId("_" + baseElement.getId());
            // add node to the main data flow
            graph.addVertex(vertex);
        }
        // read edges of the sub data flow
        final Collection> edges = subgraph.getEdges();
        for (final List list : edges) {
            for (final Edge edge : list) {
                final BpmnElement from = edge.from;
                final BpmnElement to = edge.to;
                // add edge the the main data flow
                graph.addEdge(from, to, 100);
            }
        }

        // get start and end nodes of the sub data flow and connect parallel gateways in the main flow
        // with it
        final Collection startNodes = subgraph.getStartNodes();
        for (final BpmnElement startNode : startNodes) {
            // set variables from in interface of the call activity
            startNode.setInCa(inVariables);
            graph.addEdge(parallelGateway1, startNode, 100);
        }
        final Collection endNodes = subgraph.getEndNodes();
        for (final BpmnElement endNode : endNodes) {
            // set variables from out interface of the call activity
            endNode.setOutCa(outVariables);
            graph.addEdge(endNode, parallelGateway2, 100);
        }
    }

    /**
     * Read and transform process definition into data flows
     *
     * @param classLoader
     * @param calledElementHierarchy
     * @param callActivityPath
     * @return
     */
    private Collection createSubDataFlowsFromCallActivity(final ClassLoader classLoader,
            final Collection calledElementHierarchy, final String callActivityPath) {
        // read called process
        final BpmnModelInstance submodel = Bpmn.readModelFromFile(new File(callActivityPath));

        // transform process into data flow
        final ElementGraphBuilder graphBuilder = new ElementGraphBuilder(decisionRefToPathMap,
                processIdToPathMap, messageIdToVariables, processIdToVariables, bpmnScanner);
        final Collection subgraphs = graphBuilder.createProcessGraph(submodel, callActivityPath,
                calledElementHierarchy);
        return subgraphs;
    }

    /**
     * Integrate parallel gateways into the main data flow before and after the call activity
     *
     * @param callActivity
     * @param graph
     * @param parallelGateway1
     * @param parallelGateway2
     */
    private void connectParallelGatewaysWithMainDataFlow(final CallActivity callActivity,
            final IGraph graph, final BpmnElement parallelGateway1, final BpmnElement parallelGateway2) {

        // read incoming and outgoing sequence flows of the call activity
        final SequenceFlow incomingSequenceFlow = callActivity.getIncoming().iterator().next();
        final SequenceFlow outgoingSequenceFlow = callActivity.getOutgoing().iterator().next();

        // remove edges
        graph.removeEdge(elementMap.get(incomingSequenceFlow.getId()),
                elementMap.get(callActivity.getId()));
        graph.removeEdge(elementMap.get(callActivity.getId()),
                elementMap.get(outgoingSequenceFlow.getId()));

        // link parallel gateways with the existing data flow
        graph.addEdge(elementMap.get(incomingSequenceFlow.getId()), parallelGateway1, 100);
        graph.addEdge(parallelGateway2, elementMap.get(outgoingSequenceFlow.getId()), 100);
        graph.addEdge(parallelGateway1, elementMap.get(callActivity.getId()), 100);
        graph.addEdge(elementMap.get(callActivity.getId()), parallelGateway2, 100);
    }

    /**
     * Read in- and output variables for a call activity
     *
     * @param callActivity
     * @param inVariables
     * @param outVariables
     */
    private void readCallActivityDataInterfaces(final CallActivity callActivity,
            final Collection inVariables, final Collection outVariables) {

        final ExtensionElements extensionElements = callActivity.getExtensionElements();
        if (extensionElements != null) {
            final List inputAssociations = extensionElements.getElementsQuery()
                    .filterByType(CamundaIn.class).list();
            for (final CamundaIn inputAssociation : inputAssociations) {
                final String source = inputAssociation.getCamundaSource();
                if (source != null && !source.isEmpty()) {
                    inVariables.add(source);
                }
            }
            final List outputAssociations = extensionElements.getElementsQuery()
                    .filterByType(CamundaOut.class).list();
            for (final CamundaOut outputAssociation : outputAssociations) {
                final String target = outputAssociation.getCamundaTarget();
                if (target != null && !target.isEmpty()) {
                    outVariables.add(target);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy