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

org.jbpm.bpmn2.xml.AbstractNodeHandler Maven / Gradle / Ivy

/*
 * Copyright 2021 Red Hat, Inc. and/or its affiliates.
 *
 * 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.jbpm.bpmn2.xml;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.regex.Matcher;

import org.drools.mvel.java.JavaDialect;
import org.jbpm.bpmn2.core.Association;
import org.jbpm.bpmn2.core.Definitions;
import org.jbpm.bpmn2.core.Error;
import org.jbpm.bpmn2.core.ItemDefinition;
import org.jbpm.bpmn2.core.Lane;
import org.jbpm.bpmn2.core.SequenceFlow;
import org.jbpm.bpmn2.core.Signal;
import org.jbpm.compiler.xml.Handler;
import org.jbpm.compiler.xml.Parser;
import org.jbpm.compiler.xml.ProcessBuildData;
import org.jbpm.compiler.xml.compiler.XmlDumper;
import org.jbpm.compiler.xml.core.BaseAbstractHandler;
import org.jbpm.compiler.xml.core.ExtensibleXmlParser;
import org.jbpm.process.core.ContextContainer;
import org.jbpm.process.core.context.variable.Variable;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.core.datatype.DataTypeResolver;
import org.jbpm.process.core.impl.DataTransformerRegistry;
import org.jbpm.ruleflow.core.RuleFlowProcess;
import org.jbpm.util.PatternConstants;
import org.jbpm.workflow.core.DroolsAction;
import org.jbpm.workflow.core.Node;
import org.jbpm.workflow.core.NodeContainer;
import org.jbpm.workflow.core.impl.DataAssociation;
import org.jbpm.workflow.core.impl.DataDefinition;
import org.jbpm.workflow.core.impl.DroolsConsequenceAction;
import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
import org.jbpm.workflow.core.impl.IOSpecification;
import org.jbpm.workflow.core.impl.MultiInstanceSpecification;
import org.jbpm.workflow.core.impl.NodeImpl;
import org.jbpm.workflow.core.node.ActionNode;
import org.jbpm.workflow.core.node.Assignment;
import org.jbpm.workflow.core.node.CatchLinkNode;
import org.jbpm.workflow.core.node.CompositeContextNode;
import org.jbpm.workflow.core.node.EndNode;
import org.jbpm.workflow.core.node.EventNode;
import org.jbpm.workflow.core.node.FaultNode;
import org.jbpm.workflow.core.node.ForEachNode;
import org.jbpm.workflow.core.node.StateNode;
import org.jbpm.workflow.core.node.TimerNode;
import org.jbpm.workflow.core.node.Transformation;
import org.kie.api.runtime.process.DataTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import static java.lang.Thread.currentThread;
import static org.jbpm.process.core.datatype.DataTypeResolver.fromType;
import static org.jbpm.ruleflow.core.Metadata.COMPLETION_CONDITION;
import static org.jbpm.ruleflow.core.Metadata.MAPPING_VARIABLE;
import static org.jbpm.ruleflow.core.Metadata.MAPPING_VARIABLE_INPUT;
import static org.jbpm.ruleflow.core.Metadata.VARIABLE;

public abstract class AbstractNodeHandler extends BaseAbstractHandler implements Handler {

    protected static final Logger logger = LoggerFactory.getLogger(AbstractNodeHandler.class);

    static final String PROCESS_INSTANCE_SIGNAL_EVENT = "kcontext.getProcessInstance().signalEvent(";
    static final String RUNTIME_SIGNAL_EVENT = "kcontext.getKogitoProcessRuntime().signalEvent(";

    public static final String INPUT_TYPES = "BPMN.InputTypes";
    public static final String OUTPUT_TYPES = "BPMN.OutputTypes";

    protected static final String EOL = System.getProperty("line.separator");

    public AbstractNodeHandler() {
        initValidParents();
        initValidPeers();
        this.allowNesting = true;
    }

    protected void initValidParents() {
        this.validParents = new HashSet>();
        this.validParents.add(NodeContainer.class);
    }

    protected void initValidPeers() {
        this.validPeers = new HashSet>();
        this.validPeers.add(null);
        this.validPeers.add(Lane.class);
        this.validPeers.add(Variable.class);
        this.validPeers.add(Node.class);
        this.validPeers.add(SequenceFlow.class);
        this.validPeers.add(Lane.class);
        this.validPeers.add(Association.class);
    }

    @Override
    public Object start(final String uri, final String localName, final Attributes attrs,
            final Parser parser) throws SAXException {
        parser.startElementBuilder(localName, attrs);
        final Node node = createNode(attrs);
        String id = attrs.getValue("id");
        node.setMetaData("UniqueId", id);
        final String name = attrs.getValue("name");
        node.setName(name);
        node.setMetaData(INPUT_TYPES, new HashMap());
        node.setMetaData(OUTPUT_TYPES, new HashMap());
        if ("true".equalsIgnoreCase(System.getProperty("jbpm.v5.id.strategy"))) {
            try {
                // remove starting _
                id = id.substring(1);
                // remove ids of parent nodes
                id = id.substring(id.lastIndexOf("-") + 1);
                node.setId(Integer.parseInt(id));
            } catch (NumberFormatException e) {
                // id is not in the expected format, generating a new one
                long newId = 0;
                NodeContainer nodeContainer = (NodeContainer) parser.getParent();
                for (org.kie.api.definition.process.Node n : nodeContainer.getNodes()) {
                    if (n.getId() > newId) {
                        newId = n.getId();
                    }
                }
                node.setId(++newId);
            }
        } else {
            AtomicInteger idGen = (AtomicInteger) parser.getMetaData().get("idGen");
            node.setId(idGen.getAndIncrement());
        }
        return node;
    }

    protected abstract Node createNode(Attributes attrs);

    @Override
    public Object end(final String uri, final String localName,
            final Parser parser) throws SAXException {
        final Element element = parser.endElementBuilder();
        Node node = (Node) parser.getCurrent();
        node = handleNode(node, element, uri, localName, parser);
        NodeContainer nodeContainer = (NodeContainer) parser.getParent();
        nodeContainer.addNode(node);
        ((ProcessBuildData) parser.getData()).addNode(node);
        return node;
    }

    protected Node handleNode(final Node node, final Element element, final String uri,
            final String localName, final Parser parser)
            throws SAXException {
        final String x = element.getAttribute("x");
        if (x != null && x.length() != 0) {
            try {
                node.setMetaData("x", Integer.parseInt(x));
            } catch (NumberFormatException exc) {
                throw new SAXParseException("<" + localName + "> requires an Integer 'x' attribute", parser.getLocator());
            }
        }
        final String y = element.getAttribute("y");
        if (y != null && y.length() != 0) {
            try {
                node.setMetaData("y", new Integer(y));
            } catch (NumberFormatException exc) {
                throw new SAXParseException("<" + localName + "> requires an Integer 'y' attribute", parser.getLocator());
            }
        }
        final String width = element.getAttribute("width");
        if (width != null && width.length() != 0) {
            try {
                node.setMetaData("width", new Integer(width));
            } catch (NumberFormatException exc) {
                throw new SAXParseException("<" + localName + "> requires an Integer 'width' attribute", parser.getLocator());
            }
        }
        final String height = element.getAttribute("height");
        if (height != null && height.length() != 0) {
            try {
                node.setMetaData("height", new Integer(height));
            } catch (NumberFormatException exc) {
                throw new SAXParseException("<" + localName + "> requires an Integer 'height' attribute", parser.getLocator());
            }
        }
        return node;
    }

    public abstract void writeNode(final Node node, final StringBuilder xmlDump,
            final int metaDataType);

    protected void writeNode(final String name, final Node node,
            final StringBuilder xmlDump, int metaDataType) {
        xmlDump.append("    <" + name + " ");
        xmlDump.append("id=\"" + XmlBPMNProcessDumper.getUniqueNodeId(node) + "\" ");
        if (node.getName() != null) {
            xmlDump.append("name=\"" + XmlBPMNProcessDumper.replaceIllegalCharsAttribute(node.getName()) + "\" ");
        }
        if (metaDataType == XmlBPMNProcessDumper.META_DATA_AS_NODE_PROPERTY) {
            Integer x = (Integer) node.getMetaData().get("x");
            Integer y = (Integer) node.getMetaData().get("y");
            Integer width = (Integer) node.getMetaData().get("width");
            Integer height = (Integer) node.getMetaData().get("height");
            if (x != null && x != 0) {
                xmlDump.append("g:x=\"" + x + "\" ");
            }
            if (y != null && y != 0) {
                xmlDump.append("g:y=\"" + y + "\" ");
            }
            if (width != null && width != -1) {
                xmlDump.append("g:width=\"" + width + "\" ");
            }
            if (height != null && height != -1) {
                xmlDump.append("g:height=\"" + height + "\" ");
            }
        }
    }

    protected void endNode(final StringBuilder xmlDump) {
        xmlDump.append("/>" + EOL);
    }

    protected void endNode(final String name, final StringBuilder xmlDump) {
        xmlDump.append("    " + EOL);
    }

    protected void handleScript(final ExtendedNodeImpl node, final Element element, String type) {
        NodeList nodeList = element.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            if (nodeList.item(i) instanceof Element) {
                Element xmlNode = (Element) nodeList.item(i);
                String nodeName = xmlNode.getNodeName();
                if (nodeName.equals("extensionElements")) {
                    NodeList subNodeList = xmlNode.getChildNodes();
                    for (int j = 0; j < subNodeList.getLength(); j++) {
                        org.w3c.dom.Node subXmlNode = subNodeList.item(j);
                        if (subXmlNode.getNodeName().contains(type + "-script")) {
                            List actions = node.getActions(type);
                            if (actions == null) {
                                actions = new ArrayList<>();
                                node.setActions(type, actions);
                            }
                            DroolsAction action = extractScript((Element) subXmlNode);
                            actions.add(action);
                        }
                    }
                }
            }
        }
    }

    public static DroolsAction extractScript(Element xmlNode) {
        String dialect = "mvel";
        if ("http://www.java.com/java".equals(xmlNode.getAttribute("scriptFormat"))) {
            dialect = "java";
        }
        NodeList subNodeList = xmlNode.getChildNodes();
        for (int j = 0; j < subNodeList.getLength(); j++) {
            if (subNodeList.item(j) instanceof Element) {
                Element subXmlNode = (Element) subNodeList.item(j);
                if ("script".equals(subXmlNode.getNodeName())) {
                    String consequence = subXmlNode.getTextContent();
                    return new DroolsConsequenceAction(dialect, consequence);
                }
            }
        }
        return new DroolsConsequenceAction("mvel", "");
    }

    protected void writeMetaData(final Node node, final StringBuilder xmlDump) {
        XmlBPMNProcessDumper.writeMetaData(getMetaData(node), xmlDump);
    }

    protected Map getMetaData(Node node) {
        return XmlBPMNProcessDumper.getMetaData(node.getMetaData());
    }

    protected void writeExtensionElements(Node node, final StringBuilder xmlDump) {
        if (containsExtensionElements(node)) {
            xmlDump.append("      " + EOL);
            if (node instanceof ExtendedNodeImpl) {
                writeScripts("onEntry", ((ExtendedNodeImpl) node).getActions("onEntry"), xmlDump);
                writeScripts("onExit", ((ExtendedNodeImpl) node).getActions("onExit"), xmlDump);
            }
            writeMetaData(node, xmlDump);
            xmlDump.append("      " + EOL);
        }
    }

    protected boolean containsExtensionElements(Node node) {
        if (!getMetaData(node).isEmpty()) {
            return true;
        }
        if (node instanceof ExtendedNodeImpl && ((ExtendedNodeImpl) node).containsActions()) {
            return true;
        }
        return false;
    }

    protected void writeScripts(final String type, List actions, final StringBuilder xmlDump) {
        if (!actions.isEmpty()) {
            for (DroolsAction action : actions) {
                writeScript(action, type, xmlDump);
            }
        }
    }

    public static void writeScript(final DroolsAction action, String type, final StringBuilder xmlDump) {
        if (action instanceof DroolsConsequenceAction) {
            DroolsConsequenceAction consequenceAction = (DroolsConsequenceAction) action;
            xmlDump.append("        " + EOL +
                        "          " + XmlDumper.replaceIllegalChars(consequence.trim()) + "" + EOL);
                xmlDump.append("        " + EOL);
            } else {
                xmlDump.append("/>" + EOL);
            }
        } else {
            throw new ProcessParsingValidationException(
                    "Unknown action " + action);
        }
    }

    protected void setCatchVariable(IOSpecification ioSpecification, Node node) {
        NodeImpl nodeImpl = (NodeImpl) node;
        nodeImpl.setIoSpecification(ioSpecification);
        if (node instanceof EventNode) {
            EventNode eventNode = (EventNode) node;
            findSourceMappingVar(ioSpecification.getDataOutputAssociation()).ifPresent(var -> {
                eventNode.setInputVariableName(var.getLabel());
            });
            findTargetMappingVar(ioSpecification.getDataOutputAssociation()).ifPresent(var -> {
                eventNode.getMetaData().put(MAPPING_VARIABLE, var.getLabel());
                eventNode.setVariableName(var.getLabel());
            });
        } else if (node instanceof TimerNode || node instanceof StateNode || node instanceof CatchLinkNode) {
            findTargetMappingVar(ioSpecification.getDataOutputAssociation()).ifPresent(data -> {
                nodeImpl.getMetaData().put(MAPPING_VARIABLE, data.getLabel());
            });
        }
    }

    protected void setThrowVariable(IOSpecification ioSpecification, Node node) {
        ((NodeImpl) node).setIoSpecification(ioSpecification);
        if (node instanceof ActionNode || node instanceof FaultNode || node instanceof EndNode) {
            NodeImpl mapping = (NodeImpl) node;
            findSourceMappingVar(ioSpecification.getDataInputAssociation()).ifPresent(data -> {
                if (!data.hasExpression()) {
                    mapping.getMetaData().put(MAPPING_VARIABLE, data.getLabel());
                    mapping.getMetaData().put(VARIABLE, data.getLabel());
                } else {
                    mapping.getMetaData().put(VARIABLE, data.getExpression());
                }
            });
            findTargetMappingVar(ioSpecification.getDataInputAssociation()).ifPresent(data -> {
                mapping.getMetaData().put(MAPPING_VARIABLE_INPUT, data.getLabel());
            });
        }
    }

    /*
     * This parts introduces the input/output parsing
     */

    protected Optional findTargetMappingVar(List outputs) {
        if (outputs.isEmpty()) {
            return Optional.empty();
        }

        if (outputs.get(0).getTarget() != null) {
            return Optional.of(outputs.get(0).getTarget());
        }
        return Optional.empty();
    }

    protected Optional findSourceMappingVar(List inputs) {
        if (inputs.isEmpty()) {
            return Optional.empty();
        }

        if (inputs.get(0).getAssignments().isEmpty()) {
            return Optional.of(inputs.get(0).getSources().get(0));
        } else {
            return Optional.of(inputs.get(0).getAssignments().get(0).getFrom());
        }

    }

    protected DataDefinition getVariableDataSpec(Parser parser, String propertyIdRef) {
        RuleFlowProcess process = (RuleFlowProcess) ((ProcessBuildData) parser.getData()).getMetaData(ProcessHandler.CURRENT_PROCESS);
        Optional var = process.getVariableScope().getVariables().stream().filter(e -> e.getId().equals(propertyIdRef)).findAny();
        if (var.isEmpty()) {
            return null;
        }
        Variable variable = var.get();
        return new DataDefinition(variable.getId(), variable.getName(), variable.getType().getStringType());
    }

    protected ItemDefinition getStructureRef(Parser parser, String id) {
        ProcessBuildData buildData = (ProcessBuildData) parser.getData();
        Map itemDefinitions = (Map) buildData.getMetaData("ItemDefinitions");
        return itemDefinitions.get(id);
    }

    protected IOSpecification readCatchSpecification(Parser parser, Element element) {
        IOSpecification ioSpec = new IOSpecification();
        ioSpec.getDataOutputs().addAll(readDataOutput(parser, element));
        org.w3c.dom.Node xmlNode = element.getFirstChild();
        while (xmlNode != null) {
            String nodeName = xmlNode.getNodeName();
            if ("dataOutputAssociation".equals(nodeName)) {
                readDataAssociation((Element) xmlNode, id -> ioSpec.getDataOutput().get(id), id -> getVariableDataSpec(parser, id)).ifPresent(e -> ioSpec.getDataOutputAssociation().add(e));
            }
            xmlNode = xmlNode.getNextSibling();
        }

        return ioSpec;
    }

    protected IOSpecification readThrowSpecification(Parser parser, Element element) {
        IOSpecification ioSpec = new IOSpecification();
        ioSpec.getDataInputs().addAll(readDataInput(parser, element));
        org.w3c.dom.Node xmlNode = element.getFirstChild();
        while (xmlNode != null) {
            String nodeName = xmlNode.getNodeName();
            if ("dataInputAssociation".equals(nodeName)) {
                readDataAssociation((Element) xmlNode, id -> getVariableDataSpec(parser, id), id -> ioSpec.getDataInput().get(id)).ifPresent(e -> ioSpec.getDataInputAssociation().add(e));
            }
            xmlNode = xmlNode.getNextSibling();
        }
        return ioSpec;
    }

    protected IOSpecification readIOEspecification(Parser parser, Element element) {
        org.w3c.dom.Node xmlNode = element.getFirstChild();
        IOSpecification ioSpec = new IOSpecification();
        while (xmlNode != null) {

            String nodeName = xmlNode.getNodeName();
            if ("ioSpecification".equals(nodeName)) {
                ioSpec.getDataInputs().addAll(readDataInput(parser, xmlNode));
                ioSpec.getDataOutputs().addAll(readDataOutput(parser, xmlNode));
            } else if ("dataInputAssociation".equals(nodeName)) {
                readDataAssociation((Element) xmlNode, id -> getVariableDataSpec(parser, id), id -> ioSpec.getDataInput().get(id)).ifPresent(e -> ioSpec.getDataInputAssociation().add(e));
            } else if ("dataOutputAssociation".equals(nodeName)) {
                readDataAssociation((Element) xmlNode, id -> ioSpec.getDataOutput().get(id), id -> getVariableDataSpec(parser, id)).ifPresent(e -> ioSpec.getDataOutputAssociation().add(e));
            }
            xmlNode = xmlNode.getNextSibling();
        }
        return ioSpec;
    }

    /*
     * given a parent node it reads all data inputs and creates the dataSpec input/output for that node
     */
    protected List readDataInput(Parser parser, org.w3c.dom.Node parent) {
        return readData(parser, parent, "dataInput");
    }

    protected List readDataOutput(Parser parser, org.w3c.dom.Node parent) {
        return readData(parser, parent, "dataOutput");
    }

    /*
     * this read an data definition (input or output depending on the tag)
     * dtype is used for backward compatibility
     */
    protected List readData(Parser parser, org.w3c.dom.Node parent, String tag) {
        List dataSet = new ArrayList<>();
        readChildrenElementsByTag(parent, tag).forEach(element -> {
            String id = element.getAttribute("id");
            String label = element.getAttribute("name");
            String type = null;
            String typeRef = element.getAttribute("itemSubjectRef");
            if (typeRef.isEmpty()) {
                logger.debug("{} with id {} is not pointing out to a itemSubjectRef", tag, id);
                type = element.getAttribute("dtype");
                type = type.isEmpty() ? null : type;
                if (type != null) {
                    logger.debug("{} with id {} is using an old dtype. Please use itemSubjectRef", tag, id);
                }
            } else if (getStructureRef(parser, typeRef) != null) {
                type = getStructureRef(parser, typeRef).getStructureRef();
            }
            if (type == null) {
                type = "java.lang.Object";
                logger.debug("{} with id {} is not pointing out to a valid itemSubjectRef. falling back to {}", tag, id, type);
            }
            dataSet.add(new DataDefinition(id, label, type));
        });
        return dataSet;
    }

    protected List readChildrenElementsByTag(org.w3c.dom.Node parent, String tag) {
        List elements = new ArrayList<>();
        NodeList children = parent.getChildNodes();
        for (int idx = 0; idx < children.getLength(); idx++) {
            org.w3c.dom.Node currentNode = children.item(idx);
            if (!(currentNode instanceof Element) || !tag.equals(((Element) currentNode).getNodeName())) {
                continue;
            }
            elements.add((Element) currentNode);
        }
        return elements;
    }

    protected Optional readSingleChildElementByTag(org.w3c.dom.Node parent, String tag) {
        List elements = readChildrenElementsByTag(parent, tag);
        return !elements.isEmpty() ? Optional.of(elements.get(0)) : Optional.empty();
    }

    protected Optional readDataAssociation(Element element, Function sourceResolver,
            Function targetResolver) {
        List sources = readSources(element, sourceResolver);
        DataDefinition target = readTarget(element, targetResolver);
        List assignments = readAssignments(element,
                src -> {
                    if (".".equals(src)) {
                        return sources.get(0);
                    }
                    return sourceResolver.apply(src);
                },
                dst -> {
                    if (".".equals(dst)) {
                        return target;
                    }
                    return targetResolver.apply(dst);
                });
        Transformation transformation = readTransformation(element);
        DataAssociation da = new DataAssociation(sources, target, assignments, transformation);
        if (da.getTarget() != null && da.getSources().isEmpty() && da.getAssignments().isEmpty()) {
            // incomplete description we ignore it
            logger.debug("Read incomplete data association, will be ignored\n{}", da);
            return Optional.empty();
        }

        return Optional.of(da);
    }

    private Transformation readTransformation(Element parent) {
        Optional element = readSingleChildElementByTag(parent, "transformation");
        if (element.isEmpty()) {
            return null;
        }
        String lang = element.get().getAttribute("language");
        String expression = element.get().getTextContent();

        DataTransformer transformer = DataTransformerRegistry.get().find(lang);
        if (transformer == null) {
            throw new ProcessParsingValidationException("No transformer registered for language " + lang);
        }
        return new Transformation(lang, expression);
    }

    protected List readSources(org.w3c.dom.Node parent, Function variableResolver) {
        List sources = new ArrayList<>();
        readChildrenElementsByTag(parent, "sourceRef").forEach(element -> {
            String varRef = element.getTextContent().trim();
            DataDefinition varResolved = variableResolver.apply(varRef);
            sources.add(varResolved != null ? varResolved : DataDefinition.toSimpleDefinition(varRef));
        });
        return sources;
    }

    protected DataDefinition readTarget(org.w3c.dom.Node parent, Function variableResolver) {
        Optional element = readSingleChildElementByTag(parent, "targetRef");
        if (element.isEmpty()) {
            return null;
        } else {
            String varRef = element.get().getTextContent().trim();
            DataDefinition varResolved = variableResolver.apply(varRef);
            return varResolved != null ? varResolved : DataDefinition.toSimpleDefinition(varRef);
        }
    }

    private List readAssignments(Element parent, Function sourceResolver, Function targetResolver) {
        List assignments = new ArrayList<>();
        readChildrenElementsByTag(parent, "assignment").forEach(element -> {
            Optional from = readSingleChildElementByTag(element, "from");
            Optional to = readSingleChildElementByTag(element, "to");
            String language = element.getAttribute("expressionLanguage");
            if (language == null || language.isEmpty()) {
                language = element.getAttribute("language");
            }
            String source = from.get().getTextContent();
            String target = to.get().getTextContent();
            if (!language.isEmpty()) {
                assignments.add(new Assignment(language, toDataExpression(source), toDataExpression(target)));
            } else {
                source = cleanUp(source);
                target = cleanUp(target);
                DataDefinition sourceDataSpec = isExpr(source) ? toDataExpression(source) : sourceResolver.apply(source);
                if (sourceDataSpec == null) {
                    sourceDataSpec = toDataExpression(source); // it is constant source
                }
                DataDefinition targetDataSpec = isExpr(target) ? toDataExpression(target) : targetResolver.apply(target);
                if (targetDataSpec == null) {
                    targetDataSpec = toDataExpression(target);
                }
                logger.debug("No language set for assignment {} to {}. Applying heuristics", sourceDataSpec, targetDataSpec);
                assignments.add(new Assignment(language.isEmpty() ? null : language, sourceDataSpec, targetDataSpec));
            }
        });
        return assignments;
    }

    private String cleanUp(String expression) {
        Matcher matcher = PatternConstants.PARAMETER_MATCHER.matcher(expression);
        String temp = expression;
        if (matcher.find()) {
            temp = matcher.group(1);
        }
        return temp.contains(".") ? expression : temp;
    }

    private DataDefinition toDataExpression(String expression) {
        DataDefinition dataSpec = new DataDefinition(UUID.randomUUID().toString(), "EXPRESSION (" + expression + ")", null);
        dataSpec.setExpression(expression);
        return dataSpec;

    }

    private boolean isExpr(String mvelExpression) {
        return mvelExpression != null && mvelExpression.contains("#{");
    }

    protected NodeImpl decorateMultiInstanceSpecificationSubProcess(CompositeContextNode nodeTarget, MultiInstanceSpecification multiInstanceSpecification) {
        ForEachNode forEachNode = decorateMultiInstanceSpecification(nodeTarget, multiInstanceSpecification);
        forEachNode.setMetaData("UniqueId", nodeTarget.getMetaData().get("UniqueId"));
        forEachNode.setMetaData(ProcessHandler.CONNECTIONS, nodeTarget.getMetaData(ProcessHandler.CONNECTIONS));
        forEachNode.setAutoComplete(nodeTarget.isAutoComplete());
        // nodeTarget/subprocess is invalidated by this. we get all the content and added to the for each nodes 
        // within the composite
        for (org.kie.api.definition.process.Node subNode : nodeTarget.getNodes()) {
            forEachNode.addNode(subNode);
        }

        // this is the context of each subprocess
        VariableScope subProcessVariableScope = ((VariableScope) forEachNode.getCompositeNode().getDefaultContext(VariableScope.VARIABLE_SCOPE));

        // we setup the property/data objects of the subprocess to the foreach scope of the subprocess
        // the subprocess itself scope has no effect as nodes were included in the new scope (not the old one. Look
        // at the previous for each.
        VariableScope oldSubProcessVariables = (VariableScope) nodeTarget.getDefaultContext(VariableScope.VARIABLE_SCOPE);
        oldSubProcessVariables.getVariables().forEach(subProcessVariableScope::addVariable);

        // item is the element within the collection so Collection (E1,E2....En) -> Subcontext 1 (item - E1), Subcontext 2 (item - E2)
        DataDefinition inputItem = multiInstanceSpecification.getInputDataItem();
        if (inputItem != null) {
            Variable var = new Variable();
            var.setId(inputItem.getId());
            var.setName(inputItem.getLabel());
            var.setType(DataTypeResolver.fromType(inputItem.getType(), Thread.currentThread().getContextClassLoader()));
            subProcessVariableScope.addVariable(var);
        }

        return forEachNode;
    }

    protected NodeImpl decorateMultiInstanceSpecificationActivity(NodeImpl nodeTarget, MultiInstanceSpecification multiInstanceSpecification) {
        ForEachNode forEachNode = decorateMultiInstanceSpecification(nodeTarget, multiInstanceSpecification);
        String uniqueId = (String) nodeTarget.getMetaData().get("UniqueId");
        nodeTarget.getMetaData().put("UniqueId", uniqueId + ":1");
        forEachNode.setMetaData("UniqueId", uniqueId);
        forEachNode.addNode(nodeTarget);
        forEachNode.linkIncomingConnections(Node.CONNECTION_DEFAULT_TYPE, nodeTarget.getId(), Node.CONNECTION_DEFAULT_TYPE);
        forEachNode.linkOutgoingConnections(nodeTarget.getId(), Node.CONNECTION_DEFAULT_TYPE, Node.CONNECTION_DEFAULT_TYPE);
        return forEachNode;
    }

    protected ForEachNode decorateMultiInstanceSpecification(NodeImpl nodeTarget, MultiInstanceSpecification multiInstanceSpecification) {
        ForEachNode forEachNode = new ForEachNode();
        forEachNode.setId(nodeTarget.getId());
        forEachNode.setName(nodeTarget.getName());
        nodeTarget.setMetaData("hidden", true);
        forEachNode.setIoSpecification(nodeTarget.getIoSpecification());

        DataDefinition dataInput = multiInstanceSpecification.getInputDataItem();
        DataDefinition dataOutput = multiInstanceSpecification.getOutputDataItem();
        if (dataInput != null) {
            forEachNode.setInputRef(dataInput.getLabel());
            forEachNode.addContextVariable(dataInput.getId(), dataInput.getLabel(), fromType(dataInput.getType(), currentThread().getContextClassLoader()));
            forEachNode.getIoSpecification().getDataInputAssociation().stream().filter(e -> !e.getSources().isEmpty() && e.getSources().get(0).getId().equals(dataInput.getId())).forEach(da -> {
                da.getSources().clear();
                da.getSources().add(dataInput);
            });
        }
        if (dataOutput != null) {
            forEachNode.setOutputRef(dataOutput.getLabel());
            forEachNode.addContextVariable(dataOutput.getId(), dataOutput.getLabel(), fromType(dataOutput.getType(), currentThread().getContextClassLoader()));

            forEachNode.getIoSpecification().getDataOutputAssociation().stream().filter(e -> e.getTarget().getId().equals(dataOutput.getId())).forEach(da -> {
                da.setTarget(dataOutput);
            });
        }

        if (multiInstanceSpecification.hasLoopDataInputRef()) {
            DataDefinition dataInputRef = multiInstanceSpecification.getLoopDataInputRef();
            // inputs and outputs are still processes so we need to get rid of the input of belonging to the 
            // loop
            nodeTarget.getMetaData().put("MICollectionInput", dataInputRef.getLabel());

            // this is a correction as the input collection is the source of the expr (target)
            // so target is the input collection of the node
            // so we look in the source of the data input a target is equal to the data input getting the source we get the source
            // collection at context level (subprocess or activity)
            forEachNode.getIoSpecification().getDataInputAssociation().stream().filter(e -> e.getTarget().getId().equals(dataInputRef.getId())).findAny().ifPresent(pVar -> {
                String expr = pVar.getSources().get(0).getLabel();
                forEachNode.setCollectionExpression(expr);
            });
        }

        if (multiInstanceSpecification.hasLoopDataOutputRef()) {
            // same correction as input
            // we determine the output ref and locate the source. if set the target we get the variable at that level.
            DataDefinition dataOutputRef = multiInstanceSpecification.getLoopDataOutputRef();
            nodeTarget.getMetaData().put("MICollectionOutput", dataOutputRef.getLabel());
            forEachNode.getIoSpecification().getDataOutputAssociation().stream().filter(e -> e.getSources().get(0).getId().equals(dataOutputRef.getId())).findAny().ifPresent(e -> {
                forEachNode.setOutputCollectionExpression(e.getTarget().getLabel());
            });

            // another correction colletion output is not being stored in the composite context multiinstance
            // we use foreach_output
            Iterator iterator = forEachNode.getIoSpecification().getDataOutputAssociation().iterator();
            while (iterator.hasNext()) {
                DataAssociation current = iterator.next();
                if (!current.getSources().isEmpty() && current.getSources().get(0).equals(dataOutputRef)) {
                    iterator.remove();
                }
            }
        }
        // this is just an expression
        forEachNode.setCompletionConditionExpression(multiInstanceSpecification.getCompletionCondition());
        forEachNode.setMultiInstanceSpecification(multiInstanceSpecification);

        // This variable is used for adding items computed by each subcontext.
        // after foreach is finished it will be moved to the data output ref collection of the multiinstance
        // this is the context of each subprocess
        VariableScope foreachContext = ((VariableScope) forEachNode.getCompositeNode().getDefaultContext(VariableScope.VARIABLE_SCOPE));
        Variable forEach = new Variable();
        forEach.setId("foreach_output");
        forEach.setName("foreach_output");
        forEach.setType(DataTypeResolver.fromType(Collection.class.getCanonicalName(), Thread.currentThread().getContextClassLoader()));
        foreachContext.addVariable(forEach);

        return forEachNode;

    }

    // this is only for compiling purposes
    protected MultiInstanceSpecification readMultiInstanceSpecification(Parser parser, org.w3c.dom.Node parent, IOSpecification ioSpecification) {
        MultiInstanceSpecification multiInstanceSpecification = new MultiInstanceSpecification();
        Optional multiInstanceParent = readSingleChildElementByTag(parent, "multiInstanceLoopCharacteristics");
        if (multiInstanceParent.isEmpty()) {
            return multiInstanceSpecification;
        }
        Element multiInstanceNode = multiInstanceParent.get();
        multiInstanceSpecification.setSequential(Boolean.parseBoolean(multiInstanceNode.getAttribute("isSequential")));
        readSingleChildElementByTag(multiInstanceNode, "inputDataItem").ifPresent(inputDataItem -> {
            String id = inputDataItem.getAttribute("id");
            String name = inputDataItem.getAttribute("name");
            String itemSubjectRef = inputDataItem.getAttribute("itemSubjectRef");
            ItemDefinition itemDefinition = getStructureRef(parser, itemSubjectRef);
            String structureRef = itemDefinition != null ? itemDefinition.getStructureRef() : null;
            DataDefinition input = new DataDefinition(id, name, structureRef);
            multiInstanceSpecification.setInputDataItem(input);
            if (!ioSpecification.containsInputLabel(input.getLabel())) {
                ioSpecification.getDataInputs().add(input);
            }
        });

        readSingleChildElementByTag(multiInstanceNode, "outputDataItem").ifPresent(outputDataItem -> {
            String id = outputDataItem.getAttribute("id");
            String name = outputDataItem.getAttribute("name");
            String itemSubjectRef = outputDataItem.getAttribute("itemSubjectRef");
            ItemDefinition itemDefinition = getStructureRef(parser, itemSubjectRef);
            String structureRef = itemDefinition != null ? itemDefinition.getStructureRef() : null;
            DataDefinition output = new DataDefinition(id, name, structureRef);
            multiInstanceSpecification.setOutputDataItem(output);
            if (!ioSpecification.containsOutputLabel(output.getLabel())) {
                ioSpecification.getDataOutputs().add(output);
            }
        });

        readSingleChildElementByTag(multiInstanceNode, "loopDataOutputRef").ifPresent(loopDataOutputRef -> {
            String expressiontOutput = loopDataOutputRef.getTextContent();
            if (expressiontOutput != null && !expressiontOutput.isEmpty()) {
                multiInstanceSpecification.setLoopDataOutputRef(ioSpecification.getDataOutput().get(expressiontOutput));
            }
        });

        readSingleChildElementByTag(multiInstanceNode, "loopDataInputRef").ifPresent(loopDataInputRef -> {
            String expressionInput = loopDataInputRef.getTextContent();
            if (expressionInput != null && !expressionInput.isEmpty()) {
                multiInstanceSpecification.setLoopDataInputRef(ioSpecification.getDataInput().get(expressionInput));
            }
        });

        readSingleChildElementByTag(multiInstanceNode, COMPLETION_CONDITION).ifPresent(completeCondition -> {
            String completion = completeCondition.getTextContent();
            if (completion != null && !completion.isEmpty()) {
                multiInstanceSpecification.setCompletionCondition(completion);
            }
        });
        return multiInstanceSpecification;
    }

    protected String getErrorIdForErrorCode(String errorCode, Node node) {
        org.kie.api.definition.process.NodeContainer parent = node.getParentContainer();
        while (!(parent instanceof RuleFlowProcess) && parent instanceof Node) {
            parent = ((Node) parent).getParentContainer();
        }
        if (!(parent instanceof RuleFlowProcess)) {
            throw new RuntimeException("This should never happen: !(parent instanceof RuleFlowProcess): parent is " + parent.getClass().getSimpleName());
        }
        List errors = ((Definitions) ((RuleFlowProcess) parent).getMetaData("Definitions")).getErrors();
        Error error = null;
        for (Error listError : errors) {
            if (errorCode.equals(listError.getErrorCode())) {
                error = listError;
                break;
            } else if (errorCode.equals(listError.getId())) {
                error = listError;
                break;
            }
        }
        if (error == null) {
            throw new ProcessParsingValidationException("Could not find error with errorCode " + errorCode);
        }
        return error.getId();
    }

    protected void handleThrowCompensationEventNode(final Node node, final Element element,
            final String uri, final String localName, final Parser parser) {
        org.w3c.dom.Node xmlNode = element.getFirstChild();
        if (!(node instanceof ActionNode || node instanceof EndNode)) {
            throw new IllegalArgumentException("Node is neither an ActionNode nor an EndNode but a " + node.getClass().getSimpleName());
        }
        while (xmlNode != null) {
            if ("compensateEventDefinition".equals(xmlNode.getNodeName())) {
                String activityRef = ((Element) xmlNode).getAttribute("activityRef");
                if (activityRef == null) {
                    activityRef = "";
                }
                node.setMetaData("compensation-activityRef", activityRef);

                /**
                 * waitForCompletion:
                 * BPMN 2.0 Spec, p. 304:
                 * "By default, compensation is triggered synchronously, that is the compensation throw event
                 * waits for the completion of the triggered compensation handler.
                 * Alternatively, compensation can be triggered without waiting for its completion,
                 * by setting the throw compensation event's waitForCompletion attribute to false."
                 */
                String nodeId = (String) node.getMetaData().get("UniqueId");
                String waitForCompletionString = ((Element) xmlNode).getAttribute("waitForCompletion");
                boolean waitForCompletion = true;
                if (waitForCompletionString != null && waitForCompletionString.length() > 0) {
                    waitForCompletion = Boolean.parseBoolean(waitForCompletionString);
                }
                if (!waitForCompletion) {
                    throw new ProcessParsingValidationException("Asynchronous compensation [" + nodeId + ", " + node.getName()
                            + "] is not yet supported!");
                }

            }
            xmlNode = xmlNode.getNextSibling();
        }
    }

    protected void writeThrow(IOSpecification ioSpecification, StringBuilder xmlDump) {
        for (DataDefinition input : ioSpecification.getDataInput().values()) {
            xmlDump.append("        " + EOL);
        }
        for (DataAssociation input : ioSpecification.getDataInputAssociation()) {
            xmlDump.append("      " + EOL);
            writeDataAssociation(input, xmlDump);
            xmlDump.append("      " + EOL);
        }
    }

    protected void writeCatchIO(IOSpecification ioSpecification, StringBuilder xmlDump) {
        for (DataDefinition output : ioSpecification.getDataOutput().values()) {
            xmlDump.append("        " + EOL);
        }
        for (DataAssociation output : ioSpecification.getDataOutputAssociation()) {
            xmlDump.append("      " + EOL);
            writeDataAssociation(output, xmlDump);
            xmlDump.append("      " + EOL);
        }
    }

    protected void writeIO(IOSpecification ioSpecification, StringBuilder xmlDump) {
        xmlDump.append("      " + EOL);

        for (DataDefinition input : ioSpecification.getDataInput().values()) {
            xmlDump.append("        " + EOL);
        }

        for (DataDefinition output : ioSpecification.getDataOutput().values()) {
            xmlDump.append("        " + EOL);
        }

        for (DataAssociation input : ioSpecification.getDataInputAssociation()) {
            xmlDump.append("      " + EOL);
            writeDataAssociation(input, xmlDump);
            xmlDump.append("      " + EOL);
        }
        for (DataAssociation output : ioSpecification.getDataOutputAssociation()) {
            xmlDump.append("      " + EOL);
            writeDataAssociation(output, xmlDump);
            xmlDump.append("      " + EOL);
        }
        xmlDump.append("      " + EOL);
    }

    protected void writeDataAssociation(DataAssociation input, StringBuilder xmlDump) {
        for (DataDefinition source : input.getSources()) {
            xmlDump.append("        " + source.getId() + "" + EOL);
        }
        if (input.getTarget() != null) {
            xmlDump.append("" + input.getTarget().getId() + "" + EOL);
        }
        if (!input.getAssignments().isEmpty()) {
            for (Assignment assignment : input.getAssignments()) {
                xmlDump.append("        " + EOL);
                xmlDump.append("             " + assignment.getFrom().getExpression() + "");
                xmlDump.append("             " + assignment.getTo().getExpression() + "");
                xmlDump.append("        " + EOL);
            }
        }
    }

    protected void writeMultiInstance(MultiInstanceSpecification ioSpecification, StringBuilder xmlDump) {
        if (!ioSpecification.hasLoopDataInputRef()) {
            return;
        }

        xmlDump.append("" + EOL);

        if (ioSpecification.hasLoopDataInputRef()) {
            xmlDump.append("" + ioSpecification.getLoopDataInputRef() + "" + EOL);
        }

        if (ioSpecification.hasLoopDataOutputRef()) {
            xmlDump.append("" + ioSpecification.getLoopDataOutputRef() + "" + EOL);
        }

        DataDefinition input = ioSpecification.getInputDataItem();
        if (input != null) {
            xmlDump.append("");
        }

        DataDefinition output = ioSpecification.getOutputDataItem();
        if (output != null) {
            xmlDump.append("");
        }

        xmlDump.append("" + EOL);
    }

    private static final String SIGNAL_NAMES = "signalNames";

    protected String checkSignalAndConvertToRealSignalNam(Parser parser, String signalName) {

        Signal signal = findSignalByName(parser, signalName);
        if (signal != null) {
            signalName = signal.getName();
            if (signalName == null) {
                throw new ProcessParsingValidationException("Signal definition must have a name attribute");
            }
        }

        return signalName;
    }

    protected Signal findSignalByName(Parser parser, String signalName) {
        ProcessBuildData buildData = ((ProcessBuildData) parser.getData());

        Set signalNames = (Set) buildData.getMetaData(SIGNAL_NAMES);
        if (signalNames == null) {
            signalNames = new HashSet<>();
            buildData.setMetaData(SIGNAL_NAMES, signalNames);
        }
        signalNames.add(signalName);

        Map signals = (Map) buildData.getMetaData("Signals");
        if (signals != null) {
            return signals.get(signalName);
        }

        return null;
    }

    protected String retrieveDataType(String itemSubjectRef, String dtype, Parser parser) {
        if (dtype != null && !dtype.isEmpty()) {
            return dtype;
        }

        if (itemSubjectRef != null && !itemSubjectRef.isEmpty()) {
            Map itemDefinitions = (Map) ((ProcessBuildData) parser.getData()).getMetaData("ItemDefinitions");

            return itemDefinitions.get(itemSubjectRef).getStructureRef();
        }

        return null;
    }

    /**
     * Finds the right variable by its name to make sure that when given as id it will be also matched
     *
     * @param variableName name or id of the variable
     * @param parser parser instance
     * @return returns found variable name or given 'variableName' otherwise
     */
    protected String findVariable(String variableName, final Parser parser) {
        if (variableName == null) {
            return null;
        }
        Collection parents = ((ExtensibleXmlParser) parser).getParents();

        for (Object parent : parents) {
            if (parent instanceof ContextContainer) {
                ContextContainer contextContainer = (ContextContainer) parent;
                VariableScope variableScope = (VariableScope) contextContainer.getDefaultContext(VariableScope.VARIABLE_SCOPE);
                return variableScope.getVariables().stream().filter(v -> v.matchByIdOrName(variableName)).map(v -> v.getName()).findFirst().orElse(variableName);
            }
        }

        return variableName;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy