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

org.camunda.bpm.dmn.engine.impl.DefaultDmnDecisionContext Maven / Gradle / Ivy

/* 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.camunda.bpm.dmn.engine.impl;

import static org.camunda.commons.utils.EnsureUtil.ensureNotNull;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

import org.camunda.bpm.dmn.engine.DmnDecisionRuleResult;
import org.camunda.bpm.dmn.engine.DmnDecisionTableResult;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationEvent;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationListener;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedDecisionRule;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedInput;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedOutput;
import org.camunda.bpm.dmn.engine.impl.delegate.DmnDecisionTableEvaluationEventImpl;
import org.camunda.bpm.dmn.engine.impl.delegate.DmnEvaluatedDecisionRuleImpl;
import org.camunda.bpm.dmn.engine.impl.delegate.DmnEvaluatedInputImpl;
import org.camunda.bpm.dmn.engine.impl.delegate.DmnEvaluatedOutputImpl;
import org.camunda.bpm.dmn.engine.impl.el.VariableContextScriptBindings;
import org.camunda.bpm.dmn.engine.impl.spi.el.DmnScriptEngineResolver;
import org.camunda.bpm.dmn.engine.impl.spi.el.ElExpression;
import org.camunda.bpm.dmn.engine.impl.spi.el.ElProvider;
import org.camunda.bpm.dmn.feel.impl.FeelEngine;
import org.camunda.bpm.engine.variable.Variables;
import org.camunda.bpm.engine.variable.context.VariableContext;
import org.camunda.bpm.engine.variable.impl.context.CompositeVariableContext;
import org.camunda.bpm.engine.variable.impl.context.SingleVariableContext;
import org.camunda.bpm.engine.variable.value.TypedValue;
import org.camunda.commons.utils.StringUtil;

/**
 * Context which evaluates a decision on a given input
 */
public class DefaultDmnDecisionContext {

  protected static final DmnEngineLogger LOG = DmnEngineLogger.ENGINE_LOGGER;

  protected final List evaluationListeners;
  protected final DmnScriptEngineResolver scriptEngineResolver;
  protected final ElProvider elProvider;
  protected final FeelEngine feelEngine;
  protected final String inputExpressionExpressionLanguage;
  protected final String inputEntryExpressionLanguage;
  protected final String outputEntryExpressionLanguage;

  public DefaultDmnDecisionContext(DefaultDmnEngineConfiguration configuration) {
    evaluationListeners = configuration.getDecisionTableEvaluationListeners();

    scriptEngineResolver = configuration.getScriptEngineResolver();
    elProvider = configuration.getElProvider();
    feelEngine = configuration.getFeelEngine();

    inputExpressionExpressionLanguage = configuration.getDefaultInputExpressionExpressionLanguage();
    inputEntryExpressionLanguage = configuration.getDefaultInputEntryExpressionLanguage();
    outputEntryExpressionLanguage = configuration.getDefaultOutputEntryExpressionLanguage();
  }

  /**
   * Evaluate a decision table with the given {@link VariableContext}
   *
   * @param decisionTable the decision table to evaluate
   * @param variableContext the available variable context
   * @return the result of the decision evaluation
   */
  public DmnDecisionTableResult evaluateDecisionTable(DmnDecisionTableImpl decisionTable, VariableContext variableContext) {
    DmnDecisionTableEvaluationEventImpl evaluationResult = new DmnDecisionTableEvaluationEventImpl();
    evaluationResult.setDecisionTable(decisionTable);
    evaluationResult.setExecutedDecisionElements(calculateExecutedDecisionElements(decisionTable));

    int inputSize = decisionTable.getInputs().size();
    List matchingRules = new ArrayList(decisionTable.getRules());
    for (int inputIdx = 0; inputIdx < inputSize; inputIdx++) {
      // evaluate input
      DmnDecisionTableInputImpl input = decisionTable.getInputs().get(inputIdx);
      DmnEvaluatedInput evaluatedInput = evaluateInput(input, variableContext);
      evaluationResult.getInputs().add(evaluatedInput);

      // compose local variable context out of global variable context enhanced with the value of the current input.
      VariableContext localVariableContext = getLocalVariableContext(input, evaluatedInput, variableContext);

      // filter rules applicable with this input
      matchingRules = evaluateInputForAvailableRules(inputIdx, input, matchingRules, localVariableContext);
    }

    setEvaluationOutput(decisionTable, matchingRules, variableContext, evaluationResult);
    return generateDecisionTableResult(decisionTable, evaluationResult);
  }

  protected DmnEvaluatedInput evaluateInput(DmnDecisionTableInputImpl input, VariableContext variableContext) {
    DmnEvaluatedInputImpl evaluatedInput = new DmnEvaluatedInputImpl(input);

    DmnExpressionImpl expression = input.getExpression();
    if (expression != null) {
      Object value = evaluateInputExpression(expression, variableContext);
      TypedValue typedValue = expression.getTypeDefinition().transform(value);
      evaluatedInput.setValue(typedValue);
    }
    else {
      evaluatedInput.setValue(Variables.untypedNullValue());
    }

    return evaluatedInput;
  }

  protected List evaluateInputForAvailableRules(int conditionIdx, DmnDecisionTableInputImpl input, List availableRules, VariableContext variableContext) {
    List matchingRules = new ArrayList();
    for (DmnDecisionTableRuleImpl availableRule : availableRules) {
      DmnExpressionImpl condition = availableRule.getConditions().get(conditionIdx);
      if (isConditionApplicable(input, condition, variableContext)) {
        matchingRules.add(availableRule);
      }
    }
    return matchingRules;
  }

  private VariableContext getLocalVariableContext(DmnDecisionTableInputImpl input, DmnEvaluatedInput evaluatedInput, VariableContext variableContext) {
    if (isNonEmptyExpression(input.getExpression())) {
      return CompositeVariableContext.compose(
        SingleVariableContext.singleVariable(evaluatedInput.getInputVariable(), evaluatedInput.getValue()),
        variableContext
      );
    }
    else {
      return variableContext;
    }
  }

  protected boolean isConditionApplicable(DmnDecisionTableInputImpl input, DmnExpressionImpl condition, VariableContext variableContext) {
    Object result = evaluateInputEntry(input, condition, variableContext);
    return result != null && result.equals(true);
  }

  protected void setEvaluationOutput(DmnDecisionTableImpl decisionTable, List matchingRules, VariableContext variableContext, DmnDecisionTableEvaluationEventImpl evaluationResult) {
    List decisionTableOutputs = decisionTable.getOutputs();

    List evaluatedDecisionRules = new ArrayList();
    for (DmnDecisionTableRuleImpl matchingRule : matchingRules) {
      DmnEvaluatedDecisionRule evaluatedRule = evaluateMatchingRule(decisionTableOutputs, matchingRule, variableContext);
      evaluatedDecisionRules.add(evaluatedRule);
    }
    evaluationResult.setMatchingRules(evaluatedDecisionRules);
  }

  protected DmnEvaluatedDecisionRule evaluateMatchingRule(List decisionTableOutputs, DmnDecisionTableRuleImpl matchingRule, VariableContext variableContext) {
    DmnEvaluatedDecisionRuleImpl evaluatedDecisionRule = new DmnEvaluatedDecisionRuleImpl(matchingRule);
    Map outputEntries = evaluateOutputEntries(decisionTableOutputs, matchingRule, variableContext);
    evaluatedDecisionRule.setOutputEntries(outputEntries);

    return evaluatedDecisionRule;
  }

  protected DmnDecisionTableResult generateDecisionTableResult(DmnDecisionTableImpl decisionTable, DmnDecisionTableEvaluationEventImpl evaluationResult) {
    // apply hit policy
    DmnDecisionTableEvaluationEvent evaluationEvent = decisionTable.getHitPolicyHandler().apply(evaluationResult);

    // notify listeners
    for (DmnDecisionTableEvaluationListener evaluationListener : evaluationListeners) {
      evaluationListener.notify(evaluationEvent);
    }

    return generateDecisionTableResult(evaluationEvent);
  }

  protected DmnDecisionTableResult generateDecisionTableResult(DmnDecisionTableEvaluationEvent evaluationResult) {
    List ruleResults = new ArrayList();

    if (evaluationResult.getCollectResultName() != null || evaluationResult.getCollectResultValue() != null) {
      DmnDecisionRuleResultImpl ruleResult = new DmnDecisionRuleResultImpl();
      ruleResult.putValue(evaluationResult.getCollectResultName(), evaluationResult.getCollectResultValue());
      ruleResults.add(ruleResult);
    }
    else {
      for (DmnEvaluatedDecisionRule evaluatedRule : evaluationResult.getMatchingRules()) {
        DmnDecisionRuleResultImpl ruleResult = new DmnDecisionRuleResultImpl();
        for (DmnEvaluatedOutput evaluatedOutput : evaluatedRule.getOutputEntries().values()) {
          ruleResult.putValue(evaluatedOutput.getOutputName(), evaluatedOutput.getValue());
        }
        ruleResults.add(ruleResult);
      }
    }

    return new DmnDecisionTableResultImpl(ruleResults);
  }

  protected long calculateExecutedDecisionElements(DmnDecisionTableImpl decisionTable) {
    return (decisionTable.getInputs().size() + decisionTable.getOutputs().size()) * decisionTable.getRules().size();
  }

  protected Object evaluateInputExpression(DmnExpressionImpl expression, VariableContext variableContext) {
    String expressionLanguage = expression.getExpressionLanguage();
    if (expressionLanguage == null) {
      expressionLanguage = inputExpressionExpressionLanguage;
    }

    if (isFeelExpressionLanguage(expressionLanguage)) {
      return evaluateFeelSimpleExpression(expression, variableContext);
    }
    else {
      return evaluateExpression(expressionLanguage, expression, variableContext);
    }
  }

  private Object evaluateInputEntry(DmnDecisionTableInputImpl input, DmnExpressionImpl condition, VariableContext variableContext) {
    if (isNonEmptyExpression(condition)) {
      String expressionLanguage = condition.getExpressionLanguage();
      if (expressionLanguage == null) {
        expressionLanguage = inputEntryExpressionLanguage;
      }
      if (isFeelExpressionLanguage(expressionLanguage)) {
        return evaluateFeelSimpleUnaryTests(input, condition, variableContext);
      } else {
        return evaluateExpression(expressionLanguage, condition, variableContext);
      }
    }
    else {
      return true; // input entries without expressions are true
    }
  }

  protected Map evaluateOutputEntries(List decisionTableOutputs, DmnDecisionTableRuleImpl matchingRule, VariableContext variableContext) {
    Map outputEntries = new LinkedHashMap();

    for (int outputIdx = 0; outputIdx < decisionTableOutputs.size(); outputIdx++) {
      // evaluate output entry, skip empty expressions
      DmnExpressionImpl conclusion = matchingRule.getConclusions().get(outputIdx);
      if (isNonEmptyExpression(conclusion)) {
        Object value = evaluateOutputEntry(conclusion, variableContext);

        // transform to output type
        DmnDecisionTableOutputImpl decisionTableOutput = decisionTableOutputs.get(outputIdx);
        TypedValue typedValue = decisionTableOutput.getTypeDefinition().transform(value);

        // set on result
        DmnEvaluatedOutputImpl evaluatedOutput = new DmnEvaluatedOutputImpl(decisionTableOutput, typedValue);
        outputEntries.put(decisionTableOutput.getOutputName(), evaluatedOutput);
      }
    }

    return outputEntries;
  }

  protected Object evaluateOutputEntry(DmnExpressionImpl conclusion, VariableContext variableContext) {
    String expressionLanguage = conclusion.getExpressionLanguage();
    if (expressionLanguage == null) {
      expressionLanguage = outputEntryExpressionLanguage;
    }
    if (isFeelExpressionLanguage(expressionLanguage)) {
      return evaluateFeelSimpleExpression(conclusion, variableContext);
    }
    else {
      return evaluateExpression(expressionLanguage, conclusion, variableContext);
    }
  }

  protected TypedValue evaluateFeelSimpleExpression(DmnExpressionImpl expression, VariableContext variableContext) {
    String expressionText = expression.getExpression();
    if (expressionText != null) {
      return feelEngine.evaluateSimpleExpression(expressionText, variableContext);
    }
    else {
      return null;
    }
  }

  protected Object evaluateFeelSimpleUnaryTests(DmnDecisionTableInputImpl input, DmnExpressionImpl condition, VariableContext variableContext) {
    String expressionText = condition.getExpression();
    if (expressionText != null) {
      return feelEngine.evaluateSimpleUnaryTests(expressionText, input.getInputVariable(), variableContext);
    }
    else {
      return null;
    }
  }

  protected Object evaluateExpression(String expressionLanguage, DmnExpressionImpl expression, VariableContext variableContext) {
    String expressionText = getExpressionTextForLanguage(expression, expressionLanguage);
    if (expressionText != null) {
      if(isElExpression(expressionLanguage)) {
        return evaluateElExpression(expressionLanguage, expressionText, variableContext);
      }
      else {
        return evaluateScriptExpression(expressionLanguage, variableContext, expressionText);
      }
    } else {
      return null;
    }
  }

  private Object evaluateScriptExpression(String expressionLanguage, VariableContext variableContext, String expressionText) {
    ScriptEngine scriptEngine = getScriptEngineForName(expressionLanguage);
    // wrap script engine bindings + variable context and pass enhanced
    // bindings to the script engine.
    Bindings bindings = VariableContextScriptBindings.wrap(scriptEngine.createBindings(), variableContext);
    bindings.put("variableContext", variableContext);

    try {
      return scriptEngine.eval(expressionText, bindings);
    }
    catch (ScriptException e) {
      throw LOG.unableToEvaluateExpression(expressionText, scriptEngine.getFactory().getLanguageName(), e);
    }
  }

  private Object evaluateElExpression(String expressionLanguage, String expressionText, VariableContext variableContext) {
    ElExpression elExpression = elProvider.createExpression(expressionText);
    try {
      return elExpression.getValue(variableContext);
    }
    // yes, we catch all exceptions
    catch(Exception e) {
      throw LOG.unableToEvaluateExpression(expressionText, expressionLanguage, e);
    }
  }

  // helper ///////////////////////////////////////////////////////////////////

  protected String getExpressionTextForLanguage(DmnExpressionImpl expression, String expressionLanguage) {
    String expressionText = expression.getExpression();
    if (expressionText != null) {
      if (DefaultDmnEngineConfiguration.JUEL_EXPRESSION_LANGUAGE.equals(expressionLanguage) && !StringUtil.isExpression(expressionText)) {
        return "${" + expressionText + "}";
      }
      else {
        return expressionText;
      }
    }
    else {
      return null;
    }
  }

  protected ScriptEngine getScriptEngineForName(String expressionLanguage) {
    ensureNotNull("expressionLanguage", expressionLanguage);
    ScriptEngine scriptEngine = scriptEngineResolver.getScriptEngineForLanguage(expressionLanguage);
    if (scriptEngine != null) {
      return scriptEngine;
    }
    else {
      throw LOG.noScriptEngineFoundForLanguage(expressionLanguage);
    }
  }

  protected boolean isElExpression(String expressionLanguage) {
    return DefaultDmnEngineConfiguration.JUEL_EXPRESSION_LANGUAGE.equals(expressionLanguage);
  }

  protected boolean isNonEmptyExpression(DmnExpressionImpl expression) {
    return expression != null && expression.getExpression() != null && !expression.getExpression().trim().isEmpty();
  }

  protected boolean isFeelExpressionLanguage(String expressionLanguage) {
    ensureNotNull("expressionLanguage", expressionLanguage);
    return expressionLanguage.equals(DefaultDmnEngineConfiguration.FEEL_EXPRESSION_LANGUAGE) ||
      expressionLanguage.toLowerCase().equals(DefaultDmnEngineConfiguration.FEEL_EXPRESSION_LANGUAGE_ALTERNATIVE);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy