Skip to content

Commit

Permalink
[incubator-kie-issues#1742] DMN: B-FEEL implementation (#6213)
Browse files Browse the repository at this point in the history
* [incubator-kie-issues#1659] Implemented FEELBuilder. Removing FEEL static methods. Add usage of FEELDialect inside BaseFEELTest

* [incubator-kie-issues#1187] Split EvalHelper

* [private-bamoe-issues#1659] WIP - working compilation.

* [private-bamoe-issues#1659] WIP - working compilation. Put on hold for other refactoring (incubator-kie-issues#1206)

* [private-bamoe-issues#1659] Implementing B-FEEL - overall working. Suspend to refactor functions as per incubator-kie-issues#1344

* [incubator-kie-issues#1742] Working status - compilation and tests succeed

* [incubator-kie-issues#1742] Fixed as per PR suggestion

* [incubator-kie-issues#1742] Extend test coverage

* [incubator-kie-issues#1742] WIP

* [incubator-kie-issues#1742] Fixing headers

* [incubator-kie-issues#1742] WIP

* [incubator-kie-issues#1742] Implemented BFEEL local override management

* [incubator-kie-issues#1742] Removing duplicated decision/assertions

* [incubator-kie-issues#1742] Moving/renaming/testing "equal" method to BooleanEvalHelper

* [incubator-kie-issues#1742] Extending BFEEL paradigm to all functions.
Implementing overall BFEELValueFunctionTest

* [incubator-kie-issues#1742] Fixed as per PR review

* [incubator-kie-issues#1742] Fixing license header

* [incubator-kie-issues#1742] Fixed as per PR review

* [incubator-kie-issues#1742] Fixed as per PR review

* [incubator-kie-issues#1742] Fixed as per PR review

---------

Co-authored-by: Gabriele-Cardosi <[email protected]>
  • Loading branch information
gitgabrio and Gabriele-Cardosi authored Jan 20, 2025
1 parent 0fee971 commit 829ad80
Show file tree
Hide file tree
Showing 141 changed files with 2,448 additions and 741 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private static class JSR223WrappingEC implements EvaluationContext {
private final List<FEELEvent> events;
// Defaulting FEELDialect to FEEL
private final FEELDialect dialect = FEELDialect.FEEL;

public JSR223WrappingEC(Map<String, Object> values, List<FEELEvent> events) {
this.values = Collections.unmodifiableMap(values);
this.events = events;
Expand Down Expand Up @@ -253,7 +253,7 @@ public Object getRootObject() {
}

@Override
public FEELDialect getDialect() {
public FEELDialect getFEELDialect() {
return dialect;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.kie.dmn.feel.FEEL;
import org.kie.dmn.feel.codegen.feel11.ProcessedExpression;
import org.kie.dmn.feel.lang.CompiledExpression;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.feel.lang.impl.CompiledExpressionImpl;
import org.kie.dmn.feel.lang.impl.EvaluationContextImpl;
import org.kie.dmn.feel.lang.impl.FEELImpl;
Expand Down Expand Up @@ -98,13 +99,28 @@ public EvaluatorResult evaluate(DMNRuntimeEventManager dmrem, DMNResult dmnr) {
captured.getSeverity().toString(),
MsgUtil.clipString(expressionNode.getText(), 50),
captured.getMessage());
if (captured.getSeverity() == Severity.ERROR) { // as FEEL events are being cycled, compute it here.
resultType = ResultType.FAILURE;
}
resultType = getFEELDialectAdaptedResultType(captured.getSeverity(), val, feelInstance.getFeelDialect());
}
return new EvaluatorResultImpl(val, resultType);
}

/**
* If FEELDialect is "BFEEL" and returned object is != <code>null</code>, then result type is <b>success</b>, regardless of
* given <code>Severity</code>.
* Otherwise, if severity is <code>Severity.ERROR</code>, result type is <b>failure</b>
* @param severity
* @param value
* @param feelDialect
* @return
*/
static ResultType getFEELDialectAdaptedResultType(Severity severity, Object value, FEELDialect feelDialect) {
if ((feelDialect == FEELDialect.BFEEL && value != null) || severity != Severity.ERROR) {
return ResultType.SUCCESS;
} else {
return ResultType.FAILURE;
}
}

private static DMNMessage.Severity dmnSeverityFromFEELSeverity(FEELEvent.Severity severity) {
switch (severity) {
case ERROR:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import org.kie.dmn.core.util.Msg;
import org.kie.dmn.core.util.MsgUtil;
import org.kie.dmn.core.util.NamespaceUtil;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.feel.lang.FEELProfile;
import org.kie.dmn.feel.lang.Type;
import org.kie.dmn.feel.lang.types.AliasFEELType;
Expand Down Expand Up @@ -210,7 +211,7 @@ public DMNModel compile(Definitions dmndefs, Collection<DMNModel> dmnModels, Res
model.setRuntimeTypeCheck(((DMNCompilerConfigurationImpl) dmnCompilerConfig).getOption(RuntimeTypeCheckOption.class).isRuntimeTypeCheck());
DMNCompilerConfigurationImpl cc = (DMNCompilerConfigurationImpl) dmnCompilerConfig;
List<FEELProfile> helperFEELProfiles = cc.getFeelProfiles();
DMNFEELHelper feel = new DMNFEELHelper(cc.getRootClassLoader(), helperFEELProfiles);
DMNFEELHelper feel = new DMNFEELHelper(cc.getRootClassLoader(), helperFEELProfiles, model.getFeelDialect());
DMNCompilerContext ctx = new DMNCompilerContext(feel);
ctx.setRelativeResolver(relativeResolver);
List<DMNModel> toMerge = new ArrayList<>();
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,48 @@ public class DMNFEELHelper {
private final FEEL feel;
private final FEELEventsListenerImpl listener;
private final List<FEELProfile> feelProfiles = new ArrayList<>();
private final FEELDialect feelDialect;

public DMNFEELHelper(List<FEELProfile> feelProfiles) {
this(ClassLoaderUtil.findDefaultClassLoader(), feelProfiles);
public DMNFEELHelper(List<FEELProfile> feelProfiles, FEELDialect feelDialect) {
this(ClassLoaderUtil.findDefaultClassLoader(), feelProfiles, feelDialect);
}

public DMNFEELHelper(ClassLoader classLoader, List<FEELProfile> feelProfiles) {
public DMNFEELHelper(ClassLoader classLoader, List<FEELProfile> feelProfiles, FEELDialect feelDialect) {
this.classLoader = classLoader;
this.feelProfiles.addAll(feelProfiles);
this.listener = new FEELEventsListenerImpl();
this.feelDialect = feelDialect;
this.feel = createFEELInstance();
}

private FEEL createFEELInstance() {
FEEL feel = FEELBuilder.builder().withClassloader(classLoader).withProfiles(feelProfiles).build();
feel.addListener( listener );
return feel;
FEEL toReturn = newFEELInstance();
toReturn.addListener( listener );
return toReturn;
}

/**
* Return a FEEL instance to be used in invokers/impls, which is however configured correctly accordingly to profiles
* This FEEL instance is potentially not the same shared by the compiler during the compilation phase.
*/
public FEEL newFEELInstance() {
return FEELBuilder.builder().withClassloader(classLoader).withProfiles(feelProfiles).build();
return FEELBuilder.builder().withClassloader(classLoader)
.withProfiles(feelProfiles)
.withFEELDialect(feelDialect)
.build();
}

/**
* Return a FEEL instance to be used in invokers/impls, which is however configured correctly accordingly to profiles
* but overrides the used FEELDialect
* This FEEL instance is potentially not the same shared by the compiler during the compilation phase.
*
*/
public FEEL newFEELInstance(FEELDialect overridingFeelDialect) {
return FEELBuilder.builder().withClassloader(classLoader)
.withProfiles(feelProfiles)
.withFEELDialect(overridingFeelDialect)
.build();
}

public static boolean valueMatchesInUnaryTests(List<UnaryTest> unaryTests, Object value, DMNContext dmnContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@
import org.kie.dmn.core.compiler.DMNTypeRegistryV15;
import org.kie.dmn.core.pmml.DMNImportPMMLInfo;
import org.kie.dmn.core.util.DefaultDMNMessagesManager;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.model.api.DMNModelInstrumentedBase;
import org.kie.dmn.model.api.Definitions;

import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport;
import static org.kie.dmn.core.impl.TupleIdentifier.createTupleIdentifier;
import static org.kie.dmn.core.impl.TupleIdentifier.createTupleIdentifierById;
import static org.kie.dmn.core.impl.TupleIdentifier.createTupleIdentifierByName;

public class DMNModelImpl
Expand Down Expand Up @@ -107,6 +107,8 @@ private enum SerializationFormat {

private ImportChain importChain;

private FEELDialect feelDialect;

public DMNModelImpl() {
// needed because Externalizable.
}
Expand All @@ -116,6 +118,12 @@ public DMNModelImpl(Definitions definitions) {
wireTypeRegistry(definitions);
importChain = new ImportChain(this);
messages = new DefaultDMNMessagesManager(null);
String expressionLanguage = definitions.getExpressionLanguage() != null ? definitions.getExpressionLanguage() : "";
try {
feelDialect = FEELDialect.fromNamespace(expressionLanguage);
} catch (IllegalArgumentException e) {
feelDialect = FEELDialect.FEEL;
}
}

public DMNModelImpl(Definitions dmndefs, Resource resource) {
Expand All @@ -124,6 +132,10 @@ public DMNModelImpl(Definitions dmndefs, Resource resource) {
messages = new DefaultDMNMessagesManager(resource);
}

public FEELDialect getFeelDialect() {
return feelDialect;
}

private void wireTypeRegistry(Definitions definitions) {
if (definitions instanceof org.kie.dmn.model.v1_1.TDefinitions) {
types = new DMNTypeRegistryV11(Collections.unmodifiableMap(importAliases));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.kie.dmn.core.compiler.RuntimeTypeCheckOption;
import org.kie.dmn.core.util.Msg;
import org.kie.dmn.core.util.MsgUtil;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.feel.runtime.FEELFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -670,7 +671,8 @@ private boolean evaluateDecision(DMNContext context, DMNResultImpl result, Decis
}
try {
EvaluatorResult er = decision.getEvaluator().evaluate( this, result);
if( er.getResultType() == EvaluatorResult.ResultType.SUCCESS ) {
if( er.getResultType() == EvaluatorResult.ResultType.SUCCESS ||
(((DMNModelImpl)result.getModel()).getFeelDialect().equals(FEELDialect.BFEEL) && er.getResult() != null)) {
Object value = coerceValue(decision.getResultType(), er.getResult());
try {
if (typeCheck && !d.getResultType().isAssignableValue(value)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.kie.dmn.core.impl.CompositeTypeImpl;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.core.impl.SimpleTypeImpl;
import org.kie.dmn.feel.lang.FEELDialect;
import org.kie.dmn.feel.lang.FEELProfile;
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.runtime.UnaryTest;
Expand Down Expand Up @@ -67,7 +68,7 @@ public static Either<Exception, DMNImportPMMLInfo> from(InputStream is, DMNCompi
String dfName =df.getName();
BuiltInType ft = getBuiltInTypeByDataType(df.getDataType());
List<FEELProfile> helperFEELProfiles = cc.getFeelProfiles();
DMNFEELHelper feel = new DMNFEELHelper(cc.getRootClassLoader(), helperFEELProfiles);
DMNFEELHelper feel = new DMNFEELHelper(cc.getRootClassLoader(), helperFEELProfiles, model.getFeelDialect());
List<UnaryTest> av = new ArrayList<>();
if (df.getValues() != null && !df.getValues().isEmpty() && ft != BuiltInType.UNKNOWN) {
final BuiltInType feelType = ft;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.core;

import java.util.Collections;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
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.DMNRuntime;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.core.util.DMNRuntimeUtil;
import org.kie.dmn.feel.lang.FEELDialect;

import static org.assertj.core.api.Assertions.assertThat;

public class DMNInputRuntimeBFEELTest extends BaseInterpretedVsCompiledTest {

@ParameterizedTest
@MethodSource("params")
void constraintsChecksBFEEL(boolean useExecModelCompiler) {
init(useExecModelCompiler);
final DMNRuntime runtime = DMNRuntimeUtil.createRuntime("valid_models/DMNv1_5/B-FEEL/ConstraintsChecksBFeel" +
".dmn", this.getClass());
final DMNModel dmnModel = runtime.getModel(
"http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442",
"ConstraintsChecksBFEEL");
assertThat(dmnModel).isNotNull();
assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse();
assertThat(dmnModel).isInstanceOf(DMNModelImpl.class);
assertThat(((DMNModelImpl) dmnModel).getFeelDialect()).isEqualTo(FEELDialect.BFEEL);
}

@ParameterizedTest
@MethodSource("params")
void bFeelChecks(boolean useExecModelCompiler) {
init(useExecModelCompiler);
final DMNRuntime runtime = DMNRuntimeUtil.createRuntime("valid_models/DMNv1_5/B-FEEL/BFeelChecks.dmn",
this.getClass());
final DMNModel dmnModel = runtime.getModel(
"http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442",
"BFEELChecks");
assertThat(dmnModel).isNotNull();
assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse();
assertThat(dmnModel).isInstanceOf(DMNModelImpl.class);
assertThat(((DMNModelImpl) dmnModel).getFeelDialect()).isEqualTo(FEELDialect.BFEEL);

final DMNContext ctx1 = runtime.newContext();
ctx1.set("user", "a");
final DMNResult dmnResult1 = runtime.evaluateAll(dmnModel, ctx1);
assertThat(dmnResult1.getDecisionResultByName("Decision1").getResult()).isEqualTo(false);
assertThat(dmnResult1.getDecisionResultByName("Decision2").getResult()).isEqualTo(Collections.emptyList());
}

@ParameterizedTest
@MethodSource("params")
void bFeelOverrideChecks(boolean useExecModelCompiler) {
init(useExecModelCompiler);
final DMNRuntime runtime = DMNRuntimeUtil.createRuntime("valid_models/DMNv1_5/B-FEEL/BFeelOverrideChecks.dmn",
this.getClass());
final DMNModel dmnModel = runtime.getModel(
"http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442",
"BFEELOverrideChecks");
assertThat(dmnModel).isNotNull();
assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse();
assertThat(dmnModel).isInstanceOf(DMNModelImpl.class);
assertThat(((DMNModelImpl) dmnModel).getFeelDialect()).isEqualTo(FEELDialect.FEEL);

final DMNContext ctx1 = runtime.newContext();
ctx1.set("user", "a");
final DMNResult dmnResult1 = runtime.evaluateAll(dmnModel, ctx1);
assertThat(dmnResult1.getDecisionResultByName("Decision1").getResult()).isEqualTo(false);
assertThat(dmnResult1.getDecisionResultByName("Decision2").getResult()).isNull();
assertThat(dmnResult1.getDecisionResultByName("Decision3").getResult()).isEqualTo(Collections.emptyList());
}
}
Loading

0 comments on commit 829ad80

Please sign in to comment.