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

org.kie.dmn.signavio.MultiInstanceDecisionLogic Maven / Gradle / Ivy

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.kie.dmn.signavio;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import org.kie.dmn.api.core.DMNContext;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.api.core.DMNResult;
import org.kie.dmn.api.core.ast.DMNNode;
import org.kie.dmn.api.core.event.DMNRuntimeEventManager;
import org.kie.dmn.core.api.DMNExpressionEvaluator;
import org.kie.dmn.core.api.EvaluatorResult;
import org.kie.dmn.core.api.EvaluatorResult.ResultType;
import org.kie.dmn.core.ast.DMNBaseNode;
import org.kie.dmn.core.ast.DecisionNodeImpl;
import org.kie.dmn.core.ast.EvaluatorResultImpl;
import org.kie.dmn.core.compiler.DMNCompilerContext;
import org.kie.dmn.core.compiler.DMNCompilerImpl;
import org.kie.dmn.core.compiler.DecisionCompiler;
import org.kie.dmn.core.impl.DMNContextImpl;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.core.impl.DMNResultImpl;
import org.kie.dmn.core.internal.utils.DRGAnalysisUtils;
import org.kie.dmn.core.internal.utils.DRGAnalysisUtils.DRGDependency;
import org.kie.dmn.feel.FEEL;
import org.kie.dmn.feel.runtime.functions.AllFunction;
import org.kie.dmn.feel.runtime.functions.AnyFunction;
import org.kie.dmn.feel.runtime.functions.FEELFnResult;
import org.kie.dmn.feel.runtime.functions.MaxFunction;
import org.kie.dmn.feel.runtime.functions.MinFunction;
import org.kie.dmn.feel.runtime.functions.SumFunction;
import org.kie.dmn.feel.util.NumberEvalHelper;
import org.kie.dmn.model.api.DMNElement.ExtensionElements;

import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.toSet;

@XStreamAlias("MultiInstanceDecisionLogic")
public class MultiInstanceDecisionLogic {

    @XStreamAlias("iterationExpression")
    private String iterationExpression;
    
    @XStreamAlias("iteratorShapeId")
    private String iteratorShapeId;
    
    @XStreamAlias("aggregationFunction")
    private String aggregationFunction;
    
    @XStreamAlias("topLevelDecisionId")
    private String topLevelDecisionId;
    
    public String getIterationExpression() {
        return iterationExpression;
    }

    public String getIteratorShapeId() {
        return iteratorShapeId;
    }

    public String getAggregationFunction() {
        return aggregationFunction;
    }

    public String getTopLevelDecisionId() {
        return topLevelDecisionId;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("MultiInstanceDecisionLogic [iterationExpression=").append(iterationExpression).append(", iteratorShapeId=").append(iteratorShapeId).append(", aggregationFunction=").append(aggregationFunction).append(", topLevelDecisionId=").append(topLevelDecisionId).append("]");
        return builder.toString();
    }
    
    public static class MultiInstanceDecisionNodeCompiler extends DecisionCompiler {

        private Optional getMIDL(DMNNode node) {
            if ( node instanceof DecisionNodeImpl ) {
                DecisionNodeImpl nodeImpl = (DecisionNodeImpl) node;
                ExtensionElements extElementsList = nodeImpl.getSource().getExtensionElements();
                if ( extElementsList != null && extElementsList.getAny() != null ) {
                    return extElementsList.getAny().stream()
                        .filter(MultiInstanceDecisionLogic.class::isInstance)
                        .map(MultiInstanceDecisionLogic.class::cast)
                        .findFirst();
                }
            }
            return Optional.empty();
        }
        
        @Override
        public boolean accept(DMNNode node) {
            return getMIDL(node).isPresent();
        }

        @Override
        public void compileEvaluator(DMNNode node, DMNCompilerImpl compiler, DMNCompilerContext ctx, DMNModelImpl model) {
            DecisionNodeImpl di = (DecisionNodeImpl) node;
            compiler.linkRequirements(model, di);
            
            MultiInstanceDecisionLogic midl =
                    getMIDL(node).orElseThrow(() -> new IllegalStateException("Node doesn't contain multi instance decision logic!" + node.toString()));
            
            // set the evaluator accordingly to Signavio logic.
            final MultiInstanceDecisionNodeEvaluator miEvaluator = new MultiInstanceDecisionNodeEvaluator(midl, model, di, ctx.getFeelHelper().newFEELInstance());
            di.setEvaluator(miEvaluator);
            
            compiler.addCallback((cCompiler, cCtx, cModel) -> {
                MIDDependenciesProcessor processor = new MIDDependenciesProcessor(midl, cModel);
                addRequiredDecisions(miEvaluator, processor);
                removeChildElementsFromIndex(cModel, processor);
            });
        }

        private void addRequiredDecisions(MultiInstanceDecisionNodeEvaluator miEvaluator,
                                          MIDDependenciesProcessor processor) {
            processor
                .findAllDependencies()
                .stream()
                .filter(DecisionNodeImpl.class::isInstance)
                .map(DecisionNodeImpl.class::cast)
                .forEach(miEvaluator::addReqDecision);
        }

        private void removeChildElementsFromIndex(DMNModelImpl model, MIDDependenciesProcessor processor) {
            processor.findAllChildElements().forEach(model::removeDMNNodeFromIndexes);
        }

    }

    public static class MIDDependenciesProcessor {

        private final MultiInstanceDecisionLogic mid;
        private final DMNModel model;

        public MIDDependenciesProcessor(MultiInstanceDecisionLogic mid, DMNModel model) {
            this.mid = mid;
            this.model = model;
        }

        public Collection findAllChildElements() {
            return new HashSet<>(processNode(topLevelDecision()));
        }

        public Collection findAllDependencies() {
            return DRGAnalysisUtils
                .dependencies(model, topLevelDecision())
                .stream()
                .map(DRGDependency::getDependency)
                .collect(toSet());
        }

        private Set processNode(DMNBaseNode currentNode) {
            if (currentNodeIsTheIterator(currentNode)) {
                return singleton(currentNode);
            }

            Set pathToIterator = findPathToIterator(currentNode);
            if (pathToIterator.isEmpty()) {
                return emptySet();
            }
            return extendPathBy(currentNode, pathToIterator);
        }

        private Set extendPathBy(DMNBaseNode node, Set pathToIterator) {
            Set extendedPath = new HashSet<>(pathToIterator);
            extendedPath.add(node);
            return extendedPath;
        }

        private Set findPathToIterator(DMNBaseNode currentNode) {
            return currentNode
                .getDependencies()
                .values()
                .stream()
                .map(DMNBaseNode.class::cast)
                .map(this::processNode)
                .flatMap(Collection::stream)
                .collect(toSet());
        }

        private boolean currentNodeIsTheIterator(DMNBaseNode node) {
            return mid.getIteratorShapeId().equals(node.getId());
        }

        private DMNBaseNode topLevelDecision() {
            return (DMNBaseNode) model.getDecisionById(mid.getTopLevelDecisionId());
        }

    }

    /**
     * Implements the Multi instance Decision node of Signavio as a DMNExpressionEvaluator
     */
    public static class MultiInstanceDecisionNodeEvaluator implements DMNExpressionEvaluator {
        
        private MultiInstanceDecisionLogic mi;
        private DMNModelImpl model;
        private DecisionNodeImpl di;
        private String contextIteratorName;
        private DecisionNodeImpl topLevelDecision;
        private List reqDecisions = new ArrayList<>();
        private final FEEL feel;
        
        public MultiInstanceDecisionNodeEvaluator(MultiInstanceDecisionLogic mi, DMNModelImpl model, DecisionNodeImpl di, FEEL feel) {
            this.mi = mi;
            this.model = model;
            this.di = di;
            this.feel = feel;
            contextIteratorName = model.getInputById( mi.iteratorShapeId ).getName();
            topLevelDecision = (DecisionNodeImpl) model.getDecisionById(mi.topLevelDecisionId);
        }
        
        public void addReqDecision(DecisionNodeImpl reqDecision) {
            this.reqDecisions.add(reqDecision);
        }

        @Override
        public EvaluatorResult evaluate(DMNRuntimeEventManager eventManager, DMNResult dmnr) {
            DMNResultImpl result = (DMNResultImpl) dmnr;
            DMNContext previousContext = result.getContext();
            DMNContextImpl dmnContext = (DMNContextImpl) previousContext.clone();
            result.setContext( dmnContext );
            
            List invokationResults = new ArrayList<>();
            
            try {
                Object cycleOnRaw = feel.evaluate(mi.iterationExpression, dmnContext.getAll());
                Collection cycleOn = null;
                if ( cycleOnRaw instanceof Collection ) {
                    cycleOn = (Collection) cycleOnRaw;
                } else {
                    cycleOn = Collections.singletonList(cycleOnRaw);
                }
                for ( Object cycledValue : cycleOn ) {
                    DMNContext nonCycledContext = result.getContext();
                    DMNContextImpl cyclingContext = (DMNContextImpl) nonCycledContext.clone();
                    result.setContext( cyclingContext );
                    
                    cyclingContext.set(contextIteratorName, cycledValue);
                    for (DecisionNodeImpl reqDecision : this.reqDecisions) {
                        Object subResult = reqDecision.getEvaluator().evaluate(eventManager, result).getResult();
                        cyclingContext.set(reqDecision.getName(), subResult);
                    }
                    Object evaluationResult = topLevelDecision.getEvaluator().evaluate(eventManager, result).getResult();
                    invokationResults.add(evaluationResult);
                    
                    result.setContext( nonCycledContext );
                }
            } finally {
                result.setContext( previousContext );
            }
            
            FEELFnResult r;
            switch (mi.aggregationFunction) {
                case "SUM":
                    r = SumFunction.INSTANCE.invoke(invokationResults);
                    break;
                case "MIN":
                    r = MinFunction.INSTANCE.invoke(invokationResults);
                    break;
                case "MAX":
                    r = MaxFunction.INSTANCE.invoke(invokationResults);
                    break;
                case "COUNT":
                    r = FEELFnResult.ofResult(NumberEvalHelper.getBigDecimalOrNull(invokationResults.size()));
                    break;
                case "ALLTRUE":
                    r = AllFunction.INSTANCE.invoke(invokationResults);
                    break;
                case "ANYTRUE":
                    r = AnyFunction.INSTANCE.invoke(invokationResults);
                    break;
                case "ALLFALSE":
                    FEELFnResult anyResult = AnyFunction.INSTANCE.invoke(invokationResults);
                    r = anyResult.map(b -> !b);
                    break;
                case "COLLECT":
                default:
                    r = FEELFnResult.ofResult(invokationResults);
                    break;
            }

            return new EvaluatorResultImpl(r.getOrElseThrow(e -> new RuntimeException(e.toString())), ResultType.SUCCESS);
        }
        
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy