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

org.flowable.engine.impl.bpmn.parser.BpmnParse Maven / Gradle / Ivy

There is a newer version: 7.0.1
Show newest version
/* 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 org.flowable.engine.impl.bpmn.parser;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.exceptions.XMLException;
import org.flowable.bpmn.model.BoundaryEvent;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Event;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.GraphicInfo;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.SubProcess;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.repository.EngineDeployment;
import org.flowable.common.engine.impl.event.FlowableEventSupport;
import org.flowable.common.engine.impl.util.io.InputStreamSource;
import org.flowable.common.engine.impl.util.io.StreamSource;
import org.flowable.common.engine.impl.util.io.StringStreamSource;
import org.flowable.common.engine.impl.util.io.UrlStreamSource;
import org.flowable.engine.impl.bpmn.parser.factory.ActivityBehaviorFactory;
import org.flowable.engine.impl.bpmn.parser.factory.ListenerFactory;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.io.ResourceStreamSource;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ValidationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Specific parsing of one BPMN 2.0 XML file, created by the {@link BpmnParser}.
 * 
 * @author Tijs Rademakers
 * @author Joram Barrez
 */
public class BpmnParse implements BpmnXMLConstants {

    protected static final Logger LOGGER = LoggerFactory.getLogger(BpmnParse.class);

    public static final String PROPERTYNAME_INITIAL = "initial";
    public static final String PROPERTYNAME_INITIATOR_VARIABLE_NAME = "initiatorVariableName";
    public static final String PROPERTYNAME_CONDITION = "condition";
    public static final String PROPERTYNAME_CONDITION_TEXT = "conditionText";
    public static final String PROPERTYNAME_TIMER_DECLARATION = "timerDeclarations";
    public static final String PROPERTYNAME_ISEXPANDED = "isExpanded";
    public static final String PROPERTYNAME_START_TIMER = "timerStart";
    public static final String PROPERTYNAME_COMPENSATION_HANDLER_ID = "compensationHandler";
    public static final String PROPERTYNAME_IS_FOR_COMPENSATION = "isForCompensation";
    public static final String PROPERTYNAME_ERROR_EVENT_DEFINITIONS = "errorEventDefinitions";
    public static final String PROPERTYNAME_EVENT_SUBSCRIPTION_DECLARATION = "eventDefinitions";

    protected String name;

    protected boolean validateSchema = true;
    protected boolean validateProcess = true;

    protected StreamSource streamSource;
    protected String sourceSystemId;

    protected BpmnModel bpmnModel;

    protected String targetNamespace;

    /** The deployment to which the parsed process definitions will be added. */
    protected EngineDeployment deployment;

    /** The end result of the parsing: a list of process definition. */
    protected List processDefinitions = new ArrayList<>();

    /** A map for storing sequence flow based on their id during parsing. */
    protected Map sequenceFlows;

    protected BpmnParseHandlers bpmnParserHandlers;

    protected ProcessDefinitionEntity currentProcessDefinition;

    protected Process currentProcess;

    protected FlowElement currentFlowElement;

    protected LinkedList currentSubprocessStack = new LinkedList<>();

    /**
     * Mapping containing values stored during the first phase of parsing since other elements can reference these messages.
     * 
     * All the map's elements are defined outside the process definition(s), which means that this map doesn't need to be re-initialized for each new process definition.
     */
    protected Map prefixs = new HashMap<>();

    // Factories
    protected ActivityBehaviorFactory activityBehaviorFactory;
    protected ListenerFactory listenerFactory;

    /**
     * Constructor to be called by the {@link BpmnParser}.
     */
    public BpmnParse(BpmnParser parser) {
        this.activityBehaviorFactory = parser.getActivityBehaviorFactory();
        this.listenerFactory = parser.getListenerFactory();
        this.bpmnParserHandlers = parser.getBpmnParserHandlers();
    }

    public BpmnParse deployment(EngineDeployment deployment) {
        this.deployment = deployment;
        return this;
    }

    public BpmnParse execute() {
        try {

            ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
            BpmnXMLConverter converter = new BpmnXMLConverter();

            boolean enableSafeBpmnXml = false;
            String encoding = null;
            if (processEngineConfiguration != null) {
                enableSafeBpmnXml = processEngineConfiguration.isEnableSafeBpmnXml();
                encoding = processEngineConfiguration.getXmlEncoding();
            }

            if (encoding != null) {
                bpmnModel = converter.convertToBpmnModel(streamSource, validateSchema, enableSafeBpmnXml, encoding);
            } else {
                bpmnModel = converter.convertToBpmnModel(streamSource, validateSchema, enableSafeBpmnXml);
            }

            // XSD validation goes first, then process/semantic validation
            if (validateProcess) {
                ProcessValidator processValidator = processEngineConfiguration.getProcessValidator();
                if (processValidator == null) {
                    LOGGER.warn("Process should be validated, but no process validator is configured on the process engine configuration!");
                } else {
                    List validationErrors = processValidator.validate(bpmnModel);
                    if (validationErrors != null && !validationErrors.isEmpty()) {

                        StringBuilder warningBuilder = new StringBuilder();
                        StringBuilder errorBuilder = new StringBuilder();

                        for (ValidationError error : validationErrors) {
                            if (error.isWarning()) {
                                warningBuilder.append(error);
                                warningBuilder.append("\n");
                            } else {
                                errorBuilder.append(error);
                                errorBuilder.append("\n");
                            }
                        }

                        // Throw exception if there is any error
                        if (errorBuilder.length() > 0) {
                            throw new FlowableException("Errors while parsing:\n" + errorBuilder);
                        }

                        // Write out warnings (if any)
                        if (warningBuilder.length() > 0) {
                            LOGGER.warn("Following warnings encountered during process validation: {}", warningBuilder);
                        }

                    }
                }
            }

            bpmnModel.setSourceSystemId(sourceSystemId);
            bpmnModel.setEventSupport(new FlowableEventSupport());

            // Validation successful (or no validation)

            // Attach logic to the processes (eg. map ActivityBehaviors to bpmn model elements)
            applyParseHandlers();

            // Finally, process the diagram interchange info
            processDI();

        } catch (Exception e) {
            if (e instanceof FlowableException) {
                throw (FlowableException) e;
            } else if (e instanceof XMLException) {
                throw (XMLException) e;
            } else {
                throw new FlowableException("Error parsing XML", e);
            }
        }

        return this;
    }

    public BpmnParse name(String name) {
        this.name = name;
        return this;
    }

    public BpmnParse sourceInputStream(InputStream inputStream) {
        if (name == null) {
            name("inputStream");
        }
        setStreamSource(new InputStreamSource(inputStream));
        return this;
    }

    public BpmnParse sourceResource(String resource) {
        return sourceResource(resource, null);
    }

    public BpmnParse sourceUrl(URL url) {
        if (name == null) {
            name(url.toString());
        }
        setStreamSource(new UrlStreamSource(url));
        return this;
    }

    public BpmnParse sourceUrl(String url) {
        try {
            return sourceUrl(new URL(url));
        } catch (MalformedURLException e) {
            throw new FlowableIllegalArgumentException("malformed url: " + url, e);
        }
    }

    public BpmnParse sourceResource(String resource, ClassLoader classLoader) {
        if (name == null) {
            name(resource);
        }
        setStreamSource(new ResourceStreamSource(resource, classLoader));
        return this;
    }

    public BpmnParse sourceString(String string) {
        if (name == null) {
            name("string");
        }
        setStreamSource(new StringStreamSource(string));
        return this;
    }

    protected void setStreamSource(StreamSource streamSource) {
        if (this.streamSource != null) {
            throw new FlowableIllegalArgumentException("invalid: multiple sources " + this.streamSource + " and " + streamSource);
        }
        this.streamSource = streamSource;
    }

    public BpmnParse setSourceSystemId(String sourceSystemId) {
        this.sourceSystemId = sourceSystemId;
        return this;
    }

    /**
     * Parses the 'definitions' root element
     */
    protected void applyParseHandlers() {
        sequenceFlows = new HashMap<>();
        for (Process process : bpmnModel.getProcesses()) {
            currentProcess = process;
            if (process.isExecutable()) {
                bpmnParserHandlers.parseElement(this, process);
            }
        }
    }

    public void processFlowElements(Collection flowElements) {

        // Parsing the elements is done in a strict order of types,
        // as otherwise certain information might not be available when parsing
        // a certain type.

        // Using lists as we want to keep the order in which they are defined
        List sequenceFlowToParse = new ArrayList<>();
        List boundaryEventsToParse = new ArrayList<>();

        // Flow elements that depend on other elements are parse after the first run-through
        List defferedFlowElementsToParse = new ArrayList<>();

        // Activities are parsed first
        for (FlowElement flowElement : flowElements) {

            // Sequence flow are also flow elements, but are only parsed once every activity is found
            if (flowElement instanceof SequenceFlow) {
                sequenceFlowToParse.add((SequenceFlow) flowElement);
            } else if (flowElement instanceof BoundaryEvent) {
                boundaryEventsToParse.add((BoundaryEvent) flowElement);
            } else if (flowElement instanceof Event) {
                defferedFlowElementsToParse.add(flowElement);
            } else {
                bpmnParserHandlers.parseElement(this, flowElement);
            }

        }

        // Deferred elements
        for (FlowElement flowElement : defferedFlowElementsToParse) {
            bpmnParserHandlers.parseElement(this, flowElement);
        }

        // Boundary events are parsed after all the regular activities are parsed
        for (BoundaryEvent boundaryEvent : boundaryEventsToParse) {
            bpmnParserHandlers.parseElement(this, boundaryEvent);
        }

        // sequence flows
        for (SequenceFlow sequenceFlow : sequenceFlowToParse) {
            bpmnParserHandlers.parseElement(this, sequenceFlow);
        }

    }

    // Diagram interchange
    // /////////////////////////////////////////////////////////////////

    public void processDI() {

        if (processDefinitions.isEmpty()) {
            return;
        }

        if (!bpmnModel.getLocationMap().isEmpty()) {

            // Verify if all referenced elements exist
            for (String bpmnReference : bpmnModel.getLocationMap().keySet()) {
                if (bpmnModel.getFlowElement(bpmnReference) == null) {
                    // ACT-1625: don't warn when artifacts are referenced from DI
                    if (bpmnModel.getArtifact(bpmnReference) == null) {
                        // Check if it's a Pool or Lane, then DI is ok
                        if (bpmnModel.getPool(bpmnReference) == null && bpmnModel.getLane(bpmnReference) == null) {
                            LOGGER.warn("Invalid reference in diagram interchange definition: could not find {}", bpmnReference);
                        }
                    }
                } else if (!(bpmnModel.getFlowElement(bpmnReference) instanceof FlowNode)) {
                    LOGGER.warn("Invalid reference in diagram interchange definition: {} does not reference a flow node", bpmnReference);
                }
            }

            for (String bpmnReference : bpmnModel.getFlowLocationMap().keySet()) {
                if (bpmnModel.getFlowElement(bpmnReference) == null) {
                    // ACT-1625: don't warn when artifacts are referenced from DI
                    if (bpmnModel.getArtifact(bpmnReference) == null) {
                        LOGGER.warn("Invalid reference in diagram interchange definition: could not find {}", bpmnReference);
                    }
                } else if (!(bpmnModel.getFlowElement(bpmnReference) instanceof SequenceFlow)) {
                    LOGGER.warn("Invalid reference in diagram interchange definition: {} does not reference a sequence flow", bpmnReference);
                }
            }

            for (Process process : bpmnModel.getProcesses()) {
                if (!process.isExecutable()) {
                    continue;
                }

                // Parse diagram interchange information
                ProcessDefinitionEntity processDefinition = getProcessDefinition(process.getId());
                if (processDefinition != null) {
                    processDefinition.setGraphicalNotationDefined(true);

                    for (String edgeId : bpmnModel.getFlowLocationMap().keySet()) {
                        if (bpmnModel.getFlowElement(edgeId) != null) {
                            createBPMNEdge(edgeId, bpmnModel.getFlowLocationGraphicInfo(edgeId));
                        }
                    }
                }
            }
        }
    }

    public void createBPMNEdge(String key, List graphicList) {
        FlowElement flowElement = bpmnModel.getFlowElement(key);
        if (flowElement instanceof SequenceFlow) {
            SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
            List waypoints = new ArrayList<>();
            for (GraphicInfo waypointInfo : graphicList) {
                waypoints.add((int) waypointInfo.getX());
                waypoints.add((int) waypointInfo.getY());
            }
            sequenceFlow.setWaypoints(waypoints);

        } else if (bpmnModel.getArtifact(key) != null) {
            // it's an association, so nothing to do
        } else {
            LOGGER.warn("Invalid reference in 'bpmnElement' attribute, sequenceFlow {} not found", key);
        }
    }

    public ProcessDefinitionEntity getProcessDefinition(String processDefinitionKey) {
        for (ProcessDefinitionEntity processDefinition : processDefinitions) {
            if (processDefinition.getKey().equals(processDefinitionKey)) {
                return processDefinition;
            }
        }
        return null;
    }

    /*
     * ------------------- GETTERS AND SETTERS -------------------
     */

    public boolean isValidateSchema() {
        return validateSchema;
    }

    public void setValidateSchema(boolean validateSchema) {
        this.validateSchema = validateSchema;
    }

    public boolean isValidateProcess() {
        return validateProcess;
    }

    public void setValidateProcess(boolean validateProcess) {
        this.validateProcess = validateProcess;
    }

    public List getProcessDefinitions() {
        return processDefinitions;
    }

    public String getTargetNamespace() {
        return targetNamespace;
    }

    public BpmnParseHandlers getBpmnParserHandlers() {
        return bpmnParserHandlers;
    }

    public void setBpmnParserHandlers(BpmnParseHandlers bpmnParserHandlers) {
        this.bpmnParserHandlers = bpmnParserHandlers;
    }

    public EngineDeployment getDeployment() {
        return deployment;
    }

    public void setDeployment(EngineDeployment deployment) {
        this.deployment = deployment;
    }

    public BpmnModel getBpmnModel() {
        return bpmnModel;
    }

    public void setBpmnModel(BpmnModel bpmnModel) {
        this.bpmnModel = bpmnModel;
    }

    public ActivityBehaviorFactory getActivityBehaviorFactory() {
        return activityBehaviorFactory;
    }

    public void setActivityBehaviorFactory(ActivityBehaviorFactory activityBehaviorFactory) {
        this.activityBehaviorFactory = activityBehaviorFactory;
    }

    public ListenerFactory getListenerFactory() {
        return listenerFactory;
    }

    public void setListenerFactory(ListenerFactory listenerFactory) {
        this.listenerFactory = listenerFactory;
    }

    public Map getSequenceFlows() {
        return sequenceFlows;
    }

    public ProcessDefinitionEntity getCurrentProcessDefinition() {
        return currentProcessDefinition;
    }

    public void setCurrentProcessDefinition(ProcessDefinitionEntity currentProcessDefinition) {
        this.currentProcessDefinition = currentProcessDefinition;
    }

    public FlowElement getCurrentFlowElement() {
        return currentFlowElement;
    }

    public void setCurrentFlowElement(FlowElement currentFlowElement) {
        this.currentFlowElement = currentFlowElement;
    }

    public Process getCurrentProcess() {
        return currentProcess;
    }

    public void setCurrentProcess(Process currentProcess) {
        this.currentProcess = currentProcess;
    }

    public void setCurrentSubProcess(SubProcess subProcess) {
        currentSubprocessStack.push(subProcess);
    }

    public SubProcess getCurrentSubProcess() {
        return currentSubprocessStack.peek();
    }

    public void removeCurrentSubProcess() {
        currentSubprocessStack.pop();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy