diff --git a/README.md b/README.md index 161caebddd8..00110beaeda 100644 --- a/README.md +++ b/README.md @@ -83,5 +83,15 @@ The following two commands will execute tests on machine with locale different t 1. `make test -Ptest-en` 2. `make test -DTestEn` +Documenting tips +================ + +UML diagrams have been used for architectural and design documentation. Those diagrams are in ".puml" format and have been created using the [PlantUML](https://plantuml.com/https://plantuml.com/) tool. +Plugins exists to use it in different IDE: +* [IDEA](https://plugins.jetbrains.com/plugin/7017-plantuml-integration) +* [Eclipse](https://marketplace.eclipse.org/content/plantuml-plugin) +* [VisualStudio](https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml) + + diff --git a/drools-base/src/main/java/org/drools/base/rule/EvalCondition.java b/drools-base/src/main/java/org/drools/base/rule/EvalCondition.java index aca624bb86b..3a979344b6f 100644 --- a/drools-base/src/main/java/org/drools/base/rule/EvalCondition.java +++ b/drools-base/src/main/java/org/drools/base/rule/EvalCondition.java @@ -32,11 +32,18 @@ import org.drools.base.rule.accessor.CompiledInvoker; import org.drools.base.rule.accessor.EvalExpression; import org.drools.base.rule.accessor.Wireable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class EvalCondition extends ConditionalElement implements Externalizable, Wireable { + + private static final Logger LOG = LoggerFactory.getLogger(EvalCondition.class); + + private static long warnLogCounter = 0; + private static final long serialVersionUID = 510l; protected EvalExpression expression; @@ -223,4 +230,36 @@ public void setCloned(List cloned) { public String toString() { return this.expression.toString(); } + + public static void logWarnIfImproperEval(EvalCondition evalCondition, String evalExpression) { + if (warnLogCounter == 10) { + warnLogCounter++; + LOG.warn("More eval warnings will be suppressed..."); + return; + } else if (warnLogCounter > 10) { + return; // avoid flooding the logs + } + + if (evalExpression == null || evalExpression.isEmpty()) { + return; // cannot provide a meaningful warning + } + + StringBuilder sb = new StringBuilder(); + for (Declaration declaration : evalCondition.getRequiredDeclarations()) { + if (declaration.getPattern() != null) { + sb.append("'"); + sb.append(declaration.getIdentifier()); + sb.append("' comes from previous pattern '"); + String className = declaration.getPattern().getObjectType().getClassName(); + sb.append(className.substring(className.lastIndexOf('.') + 1)); + sb.append("'. "); + } + } + if (!sb.isEmpty()) { + warnLogCounter++; + LOG.warn("In an eval expression [{}] : {}" + + "Consider placing the constraint in the pattern and removing the eval if possible," + + " as eval is not performance-efficient.", evalExpression, sb); + } + } } diff --git a/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-con.adoc b/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-con.adoc index 0d4a35c99b3..2cff887d478 100644 --- a/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-con.adoc +++ b/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-con.adoc @@ -242,10 +242,10 @@ For groups of constraints, you can use a delimiting comma `,` to use implicit `a .Example patterns with multiple constraints [source] ---- -// Person is at least 50 years old and weighs at least 80 kilograms: +// Person is more than 50 years old and weighs more than 80 kilograms: Person( age > 50, weight > 80 ) -// Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters: +// Person is more than 50 years old, weighs more than 80 kilograms, and is taller than 2 meters: Person( age > 50, weight > 80, height > 2 ) ---- diff --git a/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-elements-ref.adoc b/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-elements-ref.adoc index 09bc091dbdb..0143ef7f0ce 100644 --- a/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-elements-ref.adoc +++ b/drools-docs/src/modules/ROOT/pages/language-reference-traditional/_drl-rules-WHEN-elements-ref.adoc @@ -161,7 +161,7 @@ endif::[] rule "All full-time employees have red ID badges" when forall( $emp : Employee( type == "fulltime" ) - Employee( this == $emp, badgeColor = "red" ) ) + Employee( this == $emp, badgeColor == "red" ) ) then // True, all full-time employees have red ID badges. end @@ -174,11 +174,11 @@ To state that all facts of a given type in the working memory of the {RULE_ENGIN .Example rule with `forall` and a single pattern [source] ---- -rule "All full-time employees have red ID badges" +rule "All employees have red ID badges" when - forall( Employee( badgeColor = "red" ) ) + forall( Employee( badgeColor == "red" ) ) then - // True, all full-time employees have red ID badges. + // True, all employees have red ID badges. end ---- diff --git a/drools-docs/src/modules/ROOT/pages/language-reference/_drl-rules.adoc b/drools-docs/src/modules/ROOT/pages/language-reference/_drl-rules.adoc index f0831e34554..e1c85f980ac 100644 --- a/drools-docs/src/modules/ROOT/pages/language-reference/_drl-rules.adoc +++ b/drools-docs/src/modules/ROOT/pages/language-reference/_drl-rules.adoc @@ -1345,10 +1345,10 @@ For groups of constraints, you can use a delimiting comma `,` to use implicit `a .Example patterns with multiple constraints [source] ---- -// Person is at least 50 years old and weighs at least 80 kilograms: +// Person is more than 50 years old and weighs more than 80 kilograms: /persons[ age > 50, weight > 80 ] -// Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters: +// Person is more than 50 years old, weighs more than 80 kilograms, and is taller than 2 meters: /persons[ age > 50, weight > 80, height > 2 ] ---- @@ -2062,7 +2062,7 @@ image::language-reference/forall.png[align="center"] rule "All full-time employees have red ID badges" when forall( $emp : /employees[ type == "fulltime" ] - /employees[ this == $emp, badgeColor = "red" ] ) + /employees[ this == $emp, badgeColor == "red" ] ) then // True, all full-time employees have red ID badges. end @@ -2075,11 +2075,11 @@ To state that all facts of a given type in the working memory of the {RULE_ENGIN .Example rule with `forall` and a single pattern [source] ---- -rule "All full-time employees have red ID badges" +rule "All employees have red ID badges" when - forall( /employees[ badgeColor = "red" ] ) + forall( /employees[ badgeColor == "red" ] ) then - // True, all full-time employees have red ID badges. + // True, all employees have red ID badges. end ---- diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelApplicationPropertyProvider.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelApplicationPropertyProvider.java index dfc6e8893b5..9b079f2634b 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelApplicationPropertyProvider.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelApplicationPropertyProvider.java @@ -40,6 +40,11 @@ public Collection getApplicationProperties() { public void setApplicationProperty(String key, String value) { properties.put(key, value); } + + @Override + public void removeApplicationProperty(String key) { + properties.remove(key); + } }; } @@ -48,4 +53,6 @@ public void setApplicationProperty(String key, String value) { Collection getApplicationProperties(); void setApplicationProperty(String key, String value); + + void removeApplicationProperty(String key); } diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java index a5194eca9ca..f7537ca464a 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java @@ -40,6 +40,8 @@ public interface DroolsModelBuildContext { void setApplicationProperty(String key, String value); + void removeApplicationProperty(String key); + String getPackageName(); ClassLoader getClassLoader(); diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java index 584430d4095..c38f2391973 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java @@ -105,6 +105,11 @@ public void setApplicationProperty(String key, String value) { applicationProperties.setApplicationProperty(key, value); } + @Override + public void removeApplicationProperty(String key) { + applicationProperties.removeApplicationProperty(key); + } + @Override public String getPackageName() { return packageName; diff --git a/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/KiePackagesBuilder.java b/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/KiePackagesBuilder.java index 3189e751f7d..7c4a0af6e84 100644 --- a/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/KiePackagesBuilder.java +++ b/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/KiePackagesBuilder.java @@ -151,6 +151,7 @@ import org.kie.internal.builder.conf.PropertySpecificOption; import static java.util.stream.Collectors.toList; +import static org.drools.base.rule.EvalCondition.logWarnIfImproperEval; import static org.drools.base.rule.GroupElement.AND; import static org.drools.base.rule.GroupElement.OR; import static org.drools.compiler.rule.builder.RuleBuilder.buildTimer; @@ -669,7 +670,9 @@ private void recursivelyAddConditions(RuleContext ctx, GroupElement group, Group private EvalCondition buildEval(RuleContext ctx, EvalImpl eval) { Declaration[] declarations = Stream.of( eval.getExpr().getVariables() ).map( ctx::getDeclaration ).toArray( Declaration[]::new ); EvalExpression evalExpr = new LambdaEvalExpression(declarations, eval.getExpr()); - return new EvalCondition(evalExpr, declarations); + EvalCondition evalCondition = new EvalCondition(evalExpr, declarations); + logWarnIfImproperEval(evalCondition, eval.getExpr().predicateInformation().getStringConstraint()); + return evalCondition; } private ConditionalBranch buildConditionalConsequence(RuleContext ctx, ConditionalNamedConsequenceImpl consequence) { diff --git a/drools-mvel/src/main/java/org/drools/mvel/asm/AbstractASMEvalBuilder.java b/drools-mvel/src/main/java/org/drools/mvel/asm/AbstractASMEvalBuilder.java index 8e251f3cdb1..8ade06cbce6 100644 --- a/drools-mvel/src/main/java/org/drools/mvel/asm/AbstractASMEvalBuilder.java +++ b/drools-mvel/src/main/java/org/drools/mvel/asm/AbstractASMEvalBuilder.java @@ -38,6 +38,7 @@ import org.drools.base.rule.RuleConditionElement; import org.drools.base.rule.accessor.DeclarationScopeResolver; +import static org.drools.base.rule.EvalCondition.logWarnIfImproperEval; import static org.drools.compiler.rule.builder.PatternBuilder.buildAnalysis; import static org.drools.compiler.rule.builder.PatternBuilder.createImplicitBindings; import static org.drools.mvel.java.JavaRuleBuilderHelper.createVariableContext; @@ -90,6 +91,7 @@ private RuleConditionElement buildEval(RuleBuildContext context, EvalDescr evalD Arrays.sort(declarations, SortDeclarations.instance); EvalCondition eval = EvalConditionFactory.Factory.get().createEvalCondition(declarations); + logWarnIfImproperEval(eval, (String) evalDescr.getContent()); Map vars = createVariableContext( className, (String)evalDescr.getContent(), diff --git a/drools-mvel/src/main/java/org/drools/mvel/builder/MVELEvalBuilder.java b/drools-mvel/src/main/java/org/drools/mvel/builder/MVELEvalBuilder.java index db6ccbead3d..0859f5a901f 100644 --- a/drools-mvel/src/main/java/org/drools/mvel/builder/MVELEvalBuilder.java +++ b/drools-mvel/src/main/java/org/drools/mvel/builder/MVELEvalBuilder.java @@ -40,6 +40,7 @@ import org.drools.mvel.expr.MVELCompilationUnit; import org.drools.mvel.expr.MVELEvalExpression; +import static org.drools.base.rule.EvalCondition.logWarnIfImproperEval; import static org.drools.mvel.asm.AsmUtil.copyErrorLocation; public class MVELEvalBuilder @@ -99,6 +100,7 @@ public RuleConditionElement build(final RuleBuildContext context, false, MVELCompilationUnit.Scope.EXPRESSION ); final EvalCondition eval = EvalConditionFactory.Factory.get().createEvalCondition( previousDeclarations ); + logWarnIfImproperEval(eval, (String) evalDescr.getContent()); MVELEvalExpression expr = new MVELEvalExpression( unit, dialect.getId() ); diff --git a/kie-dmn/Developer_Guide.md b/kie-dmn/Developer_Guide.md new file mode 100644 index 00000000000..7dee4a57417 --- /dev/null +++ b/kie-dmn/Developer_Guide.md @@ -0,0 +1,188 @@ +# kie-dmn - Developer Guide + +This module (and all its submodules) contains the code that make up the DMN engine. + +The engine is responsible, in very general terms, of +1. compile the dmn model +2. validate it +3. evaluate it, against a given input; please note that execution could be statically-interpreted or dynamically-code-generated (see later on [Interpreted vs Codegen](#interpreted-vs-codegen)) + +## Structure +### DTOs +#### * [Definitions](kie-dmn-model%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fmodel%2Fapi%2FDefinitions.java): the direct representation of the original XML +#### * [DMNResource](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fassembler%2FDMNResource.java): a wrapper used to forward `Definitions` with additional informations (e.g. [ResourceWithConfiguration](..%2Fkie-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fapi%2Fio%2FResourceWithConfiguration.java)) +#### * [DMNNode](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2Fast%2FDMNNode.java): the _ast_ representation of high-level nodes inside the model; it map to **_BusinessKnowledge_**, **_Decision_**, **_DecisionService_**, **_ItemDef_**, or **_InputData_** +#### * [BaseNode](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Flang%2Fast%2FBaseNode.java): the _ast_ representation of low-level nodes inside the model (e.g. **_Boolean_**, **_AtLiteral_**, **_UnaryTest_**, **_String_**, ecc.) +#### * [CompiledFEELExpression](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fcodegen%2Ffeel11%2FCompiledFEELExpression.java): the object that represents and actually executes a specific _FEEL-expression_; it is also a `FunctionalInterface`, since it extends `Function` +#### * [InterpretedExecutableExpression](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Flang%2Fimpl%2FInterpretedExecutableExpression.java): the `CompiledFEELExpression` instantiated for statically-interpreted execution of the specific _FEEL-expression_ +#### * [CompiledExecutableExpression](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Flang%2Fimpl%2FCompiledExecutableExpression.java): the `CompiledFEELExpression` instantiated for code-generated execution of the specific _FEEL-expression_ +#### * [ProcessedExpression](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fcodegen%2Ffeel11%2FProcessedExpression.java): the result of the compilation of a specific _FEEL-expression_; in turns, it wraps the actual `CompiledFEELExpression` to be executed; please note that a _FEEL-expression_ could represent an aggregation of other sub-expressions; so, in turns, a `ProcessedExpression` may contain nested ones +#### * [DMNExpressionEvaluator](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fapi%2FDMNExpressionEvaluator.java): a `FunctionalInterface` that it is invoked by evaluation objects (e.g. `Decision`) to actually invoke the generated `ProcessedExpression`; +#### * [DMNModel](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2FDMNModel.java): the high-level result of the compilation, to be used to invoke the overall evaluation; it contains `Definitions` and all the `DMNNode`s obtained by compilation +#### * [DMNResult](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2FDMNResult.java): an instance that encapsulates all the information resulting from a DMN service invocation; +#### * [DMNDecisionResult](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2FDMNDecisionResult.java): an instance that stores the result of the evaluation of a decision + +### Code usage + +#### Compilation +The simplest way to compile a model is to instantiate a [DMNRuntime](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2FDMNRuntime.java) from its file, e.g. + ```java + DMNRuntime runtime = DMNRuntimeUtil.createRuntime("simple-item-def.dmn", this.getClass() ); + ``` +[DMNRuntimeUtil](kie-dmn-core%2Fsrc%2Ftest%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Futil%2FDMNRuntimeUtil.java) has different overrides of the `createRuntime` method + +#### Validation +To validate a dmn model, it is necessary to: +1. get an instance of [DMNValidator](kie-dmn-validation%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fvalidation%2FDMNValidator.java), built with some [DMNProfile](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fcompiler%2FDMNProfile.java); e.g. + ```java + List defaultDMNProfiles = DMNAssemblerService.getDefaultDMNProfiles(ChainedProperties.getChainedProperties(ClassLoaderUtil.findDefaultClassLoader())); + DMNValidator validator = DMNValidatorFactory.newValidator(defaultDMNProfiles); + ``` +2. invoke one of the `DMNValidator` methods; e.g. + ```java + List validate = validator.validate( + getFile("dmn14simple.dmn"), + VALIDATE_SCHEMA, VALIDATE_MODEL, VALIDATE_COMPILATION); + ``` + +### Execution +To execute a dmn model, it is necessary to: +1. get an instance of `DMNRuntime`, built around the given model; e.g. + ```java + DMNRuntime runtime = DMNRuntimeUtil.createRuntime("simple-item-def.dmn", this.getClass() ); + ``` +2. get the instance of `DMNModel` out of the former; e.g. + ```java + DMNModel dmnModel = runtime.getModel("https://github.com/kiegroup/kie-dmn/itemdef", "simple-item-def" ); + ``` +3. instantiate a [DMNContext](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2FDMNContext.java) with the required input; e.g. + ```java + DMNContext context = DMNFactory.newContext(); + context.set( "Monthly Salary", 1000 ); + ``` +4. get the `DMNResult` + ```java + DMNResult dmnResult = runtime.evaluateAll(dmnModel, context ); + ``` +5. depending on the model, the actual result could be stored in the given context, or inside a `DMNDecisionResult` + +Please note that `DMNRuntime` has different `evaluate*` methods + +### Execution flows + +#### Compilation + +The original xml is first parsed to `Definitions` by the [DMNMarshaller](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fmarshalling%2FDMNMarshaller.java), that is a simple xml-marshaller([DMNAssemblerService#addResourcesAfterRules](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fassembler%2FDMNAssemblerService.java)). + +Then, each `Definitions` is wrapped inside a `DMNResource` and _compiled_ by [DMNCompilerImpl](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fcompiler%2FDMNCompilerImpl.java). + +Each element of the `Definitions` is further translated to `DMNNode`. + +Then, the text representing each `DMNNode` is compiled by the [DecisionCompiler](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fcompiler%2FDecisionCompiler.java) and, in turn, by [FEELImpl](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Flang%2Fimpl%2FFEELImpl.java), that would return a `ProcessedExpression`. + +Inside `ProcessedExpression`, the expression is parsed to `org.antlr.v4.runtime.tree.ParseTree` by `org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser` (auto-generated), and that `ParsedTree` is visited by [ASTBuilderVisitor](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fparser%2Ffeel11%2FASTBuilderVisitor.java) to get the `BaseNode` **_ast_**, that recursively contains all the nested `BaseNode`s. + +Finally, the `CompiledFEELExpression` to be executed is returned (`ProcessedExpression#asCompiledFEELExpression`). + +![Sequence Diagram](uml/Compilation.png) + +##### Interpreted vs Codegen +The retrieved `CompiledFEELExpression` could be a _statically-interpreted_ `InterpretedExecutableExpression` (that wraps the original `BaseNode` **_ast_**) or could be a _dynamically-code-generated_ `CompiledExecutableExpression`. +In the first case, evaluation is executed by the DMN code as it is statically defined. +In the latter case, code is generated out of the given model. In that code, some variable will be directly written in the generated, speeding up its execution. +Beside that, generated code will invoke the same functions as the interpreted one. +Codegen execution is enabled in two ways: +1. adding the [DoCompileFEELProfile](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fparser%2Ffeel11%2Fprofiles%2FDoCompileFEELProfile.java) to the FEEL instantiation +2. setting the `doCompile` boolean in the `CompilerContext` (`CompilerContext.setDoCompile(true)`) + +When codegen is enabled, first the model is read and parsed as in the interpreted way; then: +1. source code is generated out of the given `BaseNode` **_ast_** (by `ASTCompilerVisitor`) +2. code is compiled in-memory to a `CompiledExecutableExpression` (by [CompilerBytecodeLoader](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fcodegen%2Ffeel11%2FCompilerBytecodeLoader.java)) +3. the above `CompiledFEELExpression` is wrapped and returned inside a `CompiledExecutableExpression` + +#### Validation +Depending on a series of flags ( `VALIDATE_SCHEMA`, `VALIDATE_MODEL`, `VALIDATE_COMPILATION`, `ANALYZE_DECISION_TABLE`), [DMNValidatorImpl](kie-dmn-validation%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fvalidation%2FDMNValidatorImpl.java) executes the validation of the given model. + +Behind the scenes, the validation also uses the _rule engine_ and the _rules_ defined in the different `kie-dmn/kie-dmn-validation/src/main/resources/org/kie/dmn/validation/DMNv1()/*.drl` files. +This validation is fired inside `private List validateModel(DMNResource mainModel, List otherModels)` + +#### Execution + +When `DMNRuntime#evaluate*` is invoked, a `DMNResult` is instantiated, containing a map with `DMNDecisionResult`s, initially marked as `NOT_EVALUATED`. + +Then, for each [DecisionNode](kie-dmn-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fapi%2Fcore%2Fast%2FDecisionNode.java): +1. the mapped `DMNDecisionResult` is marked as `EVALUATING` +2. the associated [DMNExpressionEvaluator](kie-dmn-core%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Fcore%2Fapi%2FDMNExpressionEvaluator.java) is retrieved (`DecisionNodeImpl#getEvaluator()`) +3. the `DMNExpressionEvaluator#evaluate(DMNRuntimeEventManager, DMNResult)` method is invoked (see [DMNExpressionEvaluator](#--dmnexpressionevaluatorjava-a-functionalinterface-that-it-is-invoked-by-evaluation-objects-eg-decision-to-actually-invoke-the-generated-processedexpression)) +4. inside the concrete implementation, a new `EvaluationContextImpl` is created from the `FEELImpl` instance +5. the `FEELimpl#evaluate(CompiledExpression expr, EvaluationContext ctx)` method is invoked, passing the `CompiledExpression` _**expression**_ wrapped by the current `DMNExpressionEvaluator` +6. in turns, the `ProcessedExpression#apply(EvaluationContext)` method is invoked (`ProcessedExpression` is the actual type of `CompiledExpression`) +7. this result in invocation of the wrapped `CompiledFEELExpression` **_executableFEELExpression_** (that could be an `InterpretedExecutableExpression` or a `CompiledExecutableExpression`) +8. this, recursively, iterate over all the nested expressions, invoking the [FEELFunction](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fruntime%2FFEELFunction.java)s mapped to any expression, until a given final result is provided, as `Object` +9. then, the returned object is coerced to the type requested by the decision, the result is stored in the mapped `DMNDecisionResult`, and that latter is marked as `SUCCEEDED` + +![Sequence Diagram](uml/Execution.png) + +### [BaseFEELFunction](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fruntime%2Ffunctions%2FBaseFEELFunction.java) + +Every _FEEL function_ is mapped to a concrete class extending `BaseFEELFunction`. + +During execution, this `FEELFunction` is looked-up by name, then the actual method to be invoked is looked-for, **_reflectively_**, based on the given input parameters. + +The critical point where this happen is the `BaseFEELFunction#getCandidateMethod(EvaluationContext ctx, Object[] originalInput, boolean isNamedParams)` method. + +Based on a algorithm defined in the [ScoreHelper](kie-dmn-feel%2Fsrc%2Fmain%2Fjava%2Forg%2Fkie%2Fdmn%2Ffeel%2Fruntime%2Ffunctions%2FScoreHelper.java), each `invoke` method is tested and provided a score. The one with the highest score will be used for actual function evaluation. + +### Coercion +`Coercion` is the feature for which a given object is transformed to an _equivalent_ object of a different type. +One example of that coercion is applied whenever a number, or a string representing a number, is received, in which case it is translated to `BigDecimal`. Another example is when a method expects a list, and a single object is provided: in that case, the object is _coerced_ to a singleton list. The rules for coercion are the ones provided by the DMN specification. + +During `invoke` method discovery, inside `BaseFEELFunction`, the given input parameters are _coerced_ to potentially match the ones expected by the current `invoke` method, and the _coerced_ values are stored to be used for the execution of that specific method. + + +## Development guidelines + +The main goal for the DMN engine are performance and maintainability. + +About the former, whenever important refactoring are done, it would be important to also execute [DMN Benchmarks](https://github.com/apache/incubator-kie-benchmarks/) to verify for modification of them, fixing eventual regressions. + +About the latter, it would be important to strive for simplicity of reading, instead of too-hard-to-read conciseness. +It would also be important to follow goo'ol' rules, like: +* self-explanatory method names +* self-resilient methods, i.e. a method should work by itself, and eventually manage failures, without depending on some status/assumption provided by invoking code +* meaningfully property/variable names +* focused classes, dedicated to a clear and specific task +* unit-test methods as much as possible, especially newly created ones + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNBaseNode.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNBaseNode.java index 3ceb7742deb..0ca078e34cd 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNBaseNode.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNBaseNode.java @@ -7,7 +7,7 @@ * "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 + * 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 @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import javax.xml.namespace.QName; @@ -45,17 +46,21 @@ public abstract class DMNBaseNode implements DMNNode { private Map importAliases = new HashMap<>(); + private final String id; + public DMNBaseNode() { + id = UUID.randomUUID().toString(); } public DMNBaseNode(NamedElement source) { this.source = source; + id = source != null && source.getId() != null ? source.getId() : UUID.randomUUID().toString(); } public abstract DMNType getType(); public String getId() { - return source != null ? source.getId() : null; + return id; } public String getName() { @@ -88,7 +93,7 @@ public String getModelName() { public String getIdentifierString() { String identifier = "[unnamed]"; - if( source != null ) { + if (source != null) { identifier = source.getName() != null ? source.getName() : source.getId(); } return identifier; @@ -107,11 +112,11 @@ public void setDependencies(Map dependencies) { } public void addDependency(String name, DMNNode dependency) { - this.dependencies.put( name, dependency ); + this.dependencies.put(name, dependency); } public List getInformationRequirement() { - if ( source instanceof Decision ) { + if (source instanceof Decision) { return ((Decision) source).getInformationRequirement(); } else { return Collections.emptyList(); @@ -119,9 +124,9 @@ public List getInformationRequirement() { } public List getKnowledgeRequirement() { - if ( source instanceof Decision ) { + if (source instanceof Decision) { return ((Decision) source).getKnowledgeRequirement(); - } else if( source instanceof BusinessKnowledgeModel ) { + } else if (source instanceof BusinessKnowledgeModel) { return ((BusinessKnowledgeModel) source).getKnowledgeRequirement(); } else { return Collections.emptyList(); @@ -148,5 +153,4 @@ public String toString() { builder.append("]"); return builder.toString(); } - } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/ItemDefNodeImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/ItemDefNodeImpl.java index 05fcddb2d16..ddbaa2b90a9 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/ItemDefNodeImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/ItemDefNodeImpl.java @@ -47,10 +47,6 @@ public void setItemDef(ItemDefinition itemDef) { this.itemDef = itemDef; } - public String getId() { - return itemDef.getId(); - } - public String getName() { return itemDef.getName(); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNCompilerImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNCompilerImpl.java index 8cc1eb40c26..823084f4b6f 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNCompilerImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNCompilerImpl.java @@ -384,7 +384,7 @@ private void processItemDefinitions(DMNCompilerContext ctx, DMNModelImpl model, Msg.DUPLICATED_ITEM_DEFINITION, id.getName()); } - if (id.getItemComponent() != null && id.getItemComponent().size() > 0) { + if (id.getItemComponent() != null && !id.getItemComponent().isEmpty()) { DMNCompilerHelper.checkVariableName(model, id, id.getName()); CompositeTypeImpl compType = new CompositeTypeImpl(model.getNamespace(), id.getName(), id.getId(), id.isIsCollection()); DMNType preregistered = model.getTypeRegistry().registerType(compType); @@ -402,6 +402,9 @@ private void processItemDefinitions(DMNCompilerContext ctx, DMNModelImpl model, private void processDrgElements(DMNCompilerContext ctx, DMNModelImpl model, Definitions dmndefs) { for ( DRGElement e : dmndefs.getDrgElement() ) { boolean foundIt = false; + if (e.getId() == null) { + e.setId(UUID.randomUUID().toString()); + } for( DRGElementCompiler dc : drgCompilers ) { if ( dc.accept( e ) ) { foundIt = true; @@ -436,10 +439,8 @@ private void processDrgElements(DMNCompilerContext ctx, DMNModelImpl model, Defi ds.setVariable(variable); } // continuing with normal compilation of Decision Service: - boolean foundIt = false; for (DRGElementCompiler dc : drgCompilers) { if (dc.accept(ds)) { - foundIt = true; dc.compileNode(ds, this, model); } } @@ -487,7 +488,7 @@ private void processDrgElements(DMNCompilerContext ctx, DMNModelImpl model, Defi } @FunctionalInterface - public static interface AfterProcessDrgElements { + public interface AfterProcessDrgElements { void callback(DMNCompilerImpl compiler, DMNCompilerContext ctx, DMNModelImpl model); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryAbstract.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryAbstract.java index a902e7b0b11..5aac998a9c2 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryAbstract.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryAbstract.java @@ -30,6 +30,7 @@ import org.kie.dmn.core.impl.BaseDMNTypeImpl; import org.kie.dmn.core.impl.CompositeTypeImpl; import org.kie.dmn.core.impl.SimpleTypeImpl; +import org.kie.dmn.core.impl.TupleIdentifier; import org.kie.dmn.feel.lang.Scope; import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.types.BuiltInType; @@ -41,12 +42,12 @@ public abstract class DMNTypeRegistryAbstract implements DMNTypeRegistry, FEELTypeRegistry { protected Map> types = new HashMap<>(); - protected Map aliases; + protected Map aliases; protected ScopeImpl feelTypesScope = new ScopeImpl(); // no parent scope, intentional. - protected Map feelTypesScopeChildLU = new HashMap<>(); + protected Map feelTypesScopeChildLU = new HashMap<>(); - public DMNTypeRegistryAbstract(Map aliases) { + public DMNTypeRegistryAbstract(Map aliases) { this.aliases = aliases; String feelNamespace = feelNS(); Map feelTypes = new HashMap<>( ); @@ -85,10 +86,10 @@ public Scope getItemDefScope(Scope parent) { public Type resolveFEELType(List qns) { if (qns.size() == 1) { return feelTypesScope.resolve(qns.get(0)).getType(); - } else if (qns.size() == 2 && feelTypesScopeChildLU.containsKey(qns.get(0))) { - return feelTypesScopeChildLU.get(qns.get(0)).resolve(qns.get(1)).getType(); + } else if (qns.size() == 2 && feelTypesScopeChildLU.containsKey(new TupleIdentifier(null, qns.get(0)))) { + return feelTypesScopeChildLU.get(new TupleIdentifier(null, qns.get(0))).resolve(qns.get(1)).getType(); } else { - throw new IllegalStateException("Inconsistent state when resolving for qns: " + qns.toString()); + throw new IllegalStateException("Inconsistent state when resolving for qns: " + qns); } } @@ -98,21 +99,21 @@ public Map> getTypes() { } protected void registerAsFEELType(DMNType dmnType) { - Optional optAliasKey = keyfromNS(dmnType.getNamespace()); + Optional optAliasKey = keyfromNS(dmnType.getNamespace()); Type feelType = ((BaseDMNTypeImpl) dmnType).getFeelType(); if (optAliasKey.isEmpty()) { feelTypesScope.define(new TypeSymbol(dmnType.getName(), feelType)); } else { - String aliasKey = optAliasKey.get(); + TupleIdentifier aliasKey = optAliasKey.get(); feelTypesScopeChildLU.computeIfAbsent(aliasKey, k -> { - ScopeImpl importScope = new ScopeImpl(k, feelTypesScope); - feelTypesScope.define(new TypeSymbol(k, null)); + ScopeImpl importScope = new ScopeImpl(k.getName(), feelTypesScope); + feelTypesScope.define(new TypeSymbol(k.getName(), null)); return importScope; }).define(new TypeSymbol(dmnType.getName(), feelType)); } } - private Optional keyfromNS(String ns) { + private Optional keyfromNS(String ns) { return aliases == null ? Optional.empty() : aliases.entrySet().stream().filter(kv -> kv.getValue().getNamespaceURI().equals(ns)).map(kv -> kv.getKey()).findFirst(); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV11.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV11.java index cee46ab7672..e8bd0318991 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV11.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV11.java @@ -24,12 +24,12 @@ import org.kie.dmn.api.core.DMNType; import org.kie.dmn.core.impl.SimpleTypeImpl; -import org.kie.dmn.feel.lang.types.BuiltInType; +import org.kie.dmn.core.impl.TupleIdentifier; import org.kie.dmn.model.v1_1.KieDMNModelInstrumentedBase; public class DMNTypeRegistryV11 extends DMNTypeRegistryAbstract { - public DMNTypeRegistryV11(Map aliases) { + public DMNTypeRegistryV11(Map aliases) { super(aliases); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV12.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV12.java index f146d2e1a2d..ade5a60fc3e 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV12.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV12.java @@ -27,6 +27,7 @@ import org.kie.dmn.api.core.DMNType; import org.kie.dmn.core.impl.SimpleTypeImpl; +import org.kie.dmn.core.impl.TupleIdentifier; import org.kie.dmn.feel.lang.types.BuiltInType; import org.kie.dmn.model.v1_2.KieDMNModelInstrumentedBase; @@ -38,7 +39,7 @@ public DMNTypeRegistryV12() { super(Collections.emptyMap()); } - public DMNTypeRegistryV12(Map aliases) { + public DMNTypeRegistryV12(Map aliases) { super(aliases); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV13.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV13.java index 45c0ed87f8f..4cf902bf1f3 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV13.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV13.java @@ -24,13 +24,14 @@ import org.kie.dmn.api.core.DMNType; import org.kie.dmn.core.impl.SimpleTypeImpl; +import org.kie.dmn.core.impl.TupleIdentifier; import org.kie.dmn.model.v1_3.KieDMNModelInstrumentedBase; public class DMNTypeRegistryV13 extends DMNTypeRegistryAbstract { private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); - public DMNTypeRegistryV13(Map aliases) { + public DMNTypeRegistryV13(Map aliases) { super(aliases); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV14.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV14.java index a39b5aeb421..2c7d99d719b 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV14.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV14.java @@ -24,13 +24,14 @@ import org.kie.dmn.api.core.DMNType; import org.kie.dmn.core.impl.SimpleTypeImpl; +import org.kie.dmn.core.impl.TupleIdentifier; import org.kie.dmn.model.v1_4.KieDMNModelInstrumentedBase; public class DMNTypeRegistryV14 extends DMNTypeRegistryAbstract { private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); - public DMNTypeRegistryV14(Map aliases) { + public DMNTypeRegistryV14(Map aliases) { super(aliases); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV15.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV15.java index 332fe992b9d..359edde31b8 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV15.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistryV15.java @@ -20,6 +20,7 @@ import org.kie.dmn.api.core.DMNType; import org.kie.dmn.core.impl.SimpleTypeImpl; +import org.kie.dmn.core.impl.TupleIdentifier; import org.kie.dmn.model.v1_5.KieDMNModelInstrumentedBase; import javax.xml.namespace.QName; @@ -30,7 +31,7 @@ public class DMNTypeRegistryV15 extends DMNTypeRegistryAbstract { private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); - public DMNTypeRegistryV15(Map aliases) { + public DMNTypeRegistryV15(Map aliases) { super(aliases); } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNModelImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNModelImpl.java index d100cbbc85e..6cd1b3a4b6b 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNModelImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNModelImpl.java @@ -72,36 +72,39 @@ 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 implements DMNModel, DMNMessageManager, Externalizable { - private static enum SerializationFormat { + private enum SerializationFormat { // To ensure backward compatibility, append only: DMN_XML } - private SerializationFormat serializedAs = SerializationFormat.DMN_XML; private Resource resource; private Definitions definitions; - private Map inputs = new LinkedHashMap<>(); - private Map decisions = new LinkedHashMap<>(); - private Map bkms = new LinkedHashMap<>(); - private Map itemDefs = new LinkedHashMap<>(); - private Map decisionServices = new LinkedHashMap<>(); + private Map inputs = new LinkedHashMap<>(); + private Map decisions = new LinkedHashMap<>(); + private Map bkms = new LinkedHashMap<>(); + private Map itemDefs = new LinkedHashMap<>(); + private Map decisionServices = new LinkedHashMap<>(); + private Map pmmlImportInfo = new HashMap<>(); + private Map importAliases = new HashMap<>(); // these are messages created at loading/compilation time private DMNMessageManager messages; private DMNTypeRegistry types; /** - * a compile-time preference to indicate if type-check should be performed during runtime evaluation. + * a compile-time preference to indicate if type-check should be performed during runtime evaluation. */ private boolean runtimeTypeCheck = false; - private Map importAliases = new HashMap<>(); private ImportChain importChain; public DMNModelImpl() { @@ -173,12 +176,12 @@ public String nameInCurrentModel(DMNNode node) { } public void addInput(InputDataNode idn) { - computeDRGElementModelLocalId(idn).forEach(id -> inputs.put(id, idn)); + computeDRGElementModelLocalId(idn).forEach(id -> inputs.put(createTupleIdentifier(id, nameInCurrentModel(idn)), idn)); } @Override public InputDataNode getInputById(String id) { - return this.inputs.get( id ); + return this.inputs.get( new TupleIdentifier(id, null) ); } @Override @@ -186,12 +189,7 @@ public InputDataNode getInputByName(String name) { if ( name == null ) { return null; } - for ( InputDataNode in : this.inputs.values() ) { - if (Objects.equals(name, nameInCurrentModel(in))) { - return in; - } - } - return null; + return inputs.get(new TupleIdentifier(null, name)); } @Override @@ -200,7 +198,7 @@ public Set getInputs() { } public void addDecision(DecisionNode dn) { - computeDRGElementModelLocalId(dn).forEach(id -> decisions.put(id, dn)); + computeDRGElementModelLocalId(dn).forEach(id -> decisions.put(createTupleIdentifier(id, nameInCurrentModel(dn)), dn)); } private List computeDRGElementModelLocalId(DMNNode node) { @@ -220,7 +218,7 @@ private List computeDRGElementModelLocalId(DMNNode node) { @Override public DecisionNode getDecisionById(String id) { - return this.decisions.get(id); + return id != null ? this.decisions.get(new TupleIdentifier(id, null)) : null; } @Override @@ -228,12 +226,7 @@ public DecisionNode getDecisionByName(String name) { if ( name == null ) { return null; } - for ( DecisionNode dn : this.decisions.values() ) { - if (Objects.equals(name, nameInCurrentModel(dn))) { - return dn; - } - } - return null; + return decisions.get(new TupleIdentifier(null, name)); } @Override @@ -262,23 +255,18 @@ public Set getRequiredInputsForDecisionId(String decisionId) { } public void addDecisionService(DecisionServiceNode dsn) { - computeDRGElementModelLocalId(dsn).forEach(id -> decisionServices.put(id, dsn)); + computeDRGElementModelLocalId(dsn).forEach(id -> decisionServices.put(createTupleIdentifier(id, dsn.getName()), dsn)); } public DecisionServiceNode getDecisionServiceById(String id) { - return this.decisionServices.get(id); + return this.decisionServices.get(new TupleIdentifier(id, null)); } public DecisionServiceNode getDecisionServiceByName(String name) { if (name == null) { return null; } - for (DecisionServiceNode dn : this.decisionServices.values()) { - if (Objects.equals(name, dn.getName())) { - return dn; - } - } - return null; + return decisionServices.get(new TupleIdentifier(null, name)); } @Override @@ -287,12 +275,12 @@ public Collection getDecisionServices() { } public void addBusinessKnowledgeModel(BusinessKnowledgeModelNode bkm) { - computeDRGElementModelLocalId(bkm).forEach(id -> bkms.put(id, bkm)); + computeDRGElementModelLocalId(bkm).forEach(id -> bkms.put(createTupleIdentifier(id, bkm.getName()), bkm)); } @Override public BusinessKnowledgeModelNode getBusinessKnowledgeModelById(String id) { - return this.bkms.get( id ); + return this.bkms.get( new TupleIdentifier(id, null) ); } @Override @@ -300,17 +288,12 @@ public BusinessKnowledgeModelNode getBusinessKnowledgeModelByName(String name) { if ( name == null ) { return null; } - for ( BusinessKnowledgeModelNode bkm : this.bkms.values() ) { - if (Objects.equals(name, bkm.getName())) { - return bkm; - } - } - return null; + return bkms.get(new TupleIdentifier(null, name)); } @Override public Set getBusinessKnowledgeModels() { - return this.bkms.values().stream().collect(Collectors.toCollection(LinkedHashSet::new)); + return new LinkedHashSet<>(this.bkms.values()); } private void collectRequiredInputs(Collection deps, Set inputs) { @@ -327,12 +310,13 @@ private void collectRequiredInputs(Collection deps, Set public void addItemDefinition(ItemDefNode idn) { // if ID is null, generate an ID for it - this.itemDefs.put( idn.getId() != null ? idn.getId() : "_"+this.itemDefs.size(), idn ); + String id = idn.getId() != null ? idn.getId() : "_" + this.itemDefs.size(); + this.itemDefs.put( createTupleIdentifier(id, idn.getName()), idn ); } @Override public ItemDefNode getItemDefinitionById(String id) { - return this.itemDefs.get( id ); + return this.itemDefs.get( new TupleIdentifier(id, null) ); } @Override @@ -340,17 +324,12 @@ public ItemDefNode getItemDefinitionByName(String name) { if ( name == null ) { return null; } - for ( ItemDefNode in : this.itemDefs.values() ) { - if (Objects.equals(name, in.getName())) { - return in; - } - } - return null; + return itemDefs.get(new TupleIdentifier(null, name)); } @Override public Set getItemDefinitions() { - return this.itemDefs.values().stream().collect( Collectors.toCollection(LinkedHashSet::new) ); + return new LinkedHashSet<>(this.itemDefs.values()); } @Override @@ -477,21 +456,23 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept public void setImportAliasForNS(String iAlias, String iNS, String iModelName) { if (getImportAliasFor(iNS, iModelName).isEmpty()) { - this.importAliases.put(iAlias, new QName(iNS, iModelName)); + this.importAliases.put(createTupleIdentifierByName(iAlias), new QName(iNS, iModelName)); } } public Map getImportAliasesForNS() { - return Collections.unmodifiableMap(this.importAliases); + return importAliases.entrySet() + .stream().collect(Collectors.toMap(tupleIdentifierQNameEntry -> tupleIdentifierQNameEntry.getKey().getName(), + Entry::getValue)); } public Optional getImportAliasFor(String ns, String iModelName) { QName lookup = new QName(ns, iModelName); - return this.importAliases.entrySet().stream().filter(kv -> kv.getValue().equals(lookup)).map(kv -> kv.getKey()).findFirst(); + return this.importAliases.entrySet().stream().filter(kv -> kv.getValue().equals(lookup)).map(kv -> kv.getKey().getName()).findFirst(); } public QName getImportNamespaceAndNameforAlias(String iAlias) { - return this.importAliases.get(iAlias); + return this.importAliases.get(createTupleIdentifierByName(iAlias)); } public void addImportChainChild(ImportChain child, String alias) { @@ -510,6 +491,23 @@ public List getImportChainDirectChildModels() { return this.importChain.getImportChainDirectChildModels(); } + + @Override + public void addAllUnfiltered(List messages) { + this.messages.addAllUnfiltered( messages ); + } + + public void addPMMLImportInfo(DMNImportPMMLInfo info) { + this.pmmlImportInfo.put(createTupleIdentifierByName(info.getImportName()), info); + } + + public Map getPmmlImportInfo() { + return pmmlImportInfo.entrySet() + .stream().collect(Collectors.toMap(tupleIdentifierQNameEntry -> tupleIdentifierQNameEntry.getKey().getName(), + Entry::getValue)); + + } + private static class ImportChain { private final String alias; private final DMNModel node; @@ -565,19 +563,4 @@ public List getImportChainDirectChildModels() { } } - @Override - public void addAllUnfiltered(List messages) { - this.messages.addAllUnfiltered( messages ); - } - - private Map pmmlImportInfo = new HashMap<>(); - - public void addPMMLImportInfo(DMNImportPMMLInfo info) { - this.pmmlImportInfo.put(info.getImportName(), info); - } - - public Map getPmmlImportInfo() { - return Collections.unmodifiableMap(pmmlImportInfo); - } - } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/TupleIdentifier.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/TupleIdentifier.java new file mode 100644 index 00000000000..16b3758d79d --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/TupleIdentifier.java @@ -0,0 +1,86 @@ +/** + * 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.impl; + +import java.util.Objects; + +public class TupleIdentifier { + + private final String id; + private final String name; + + static final String AUTO_GENERATED_ID_PREFIX = "auto-generated-id"; + static final String AUTO_GENERATED_NAME_PREFIX = "auto-generated-name"; + + static TupleIdentifier createTupleIdentifier(String id, String name) { + return new TupleIdentifier(id, name); + } + + static TupleIdentifier createTupleIdentifierById(String id) { + return new TupleIdentifier(id, generateNameFromId(id)); + } + + static TupleIdentifier createTupleIdentifierByName(String name) { + return new TupleIdentifier(generateIdFromName(name), name); + } + + static String generateIdFromName(String name) { + return String.format("%s-%s", AUTO_GENERATED_ID_PREFIX, Objects.hash(name)); + } + + static String generateNameFromId(String id) { + return String.format("%s-%s", AUTO_GENERATED_NAME_PREFIX, Objects.hash(id)); + } + + public TupleIdentifier(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TupleIdentifier that)) { + return false; + } + // This "null" availability it is to allow for search based only on id or name + if (id == null || that.id == null) { + return Objects.equals(name, that.name); + } else if (name == null || that.name == null) { + return Objects.equals(id, that.id); + } else { + return Objects.equals(id, that.id) && Objects.equals(name, that.name); + } + } + + @Override + public int hashCode() { + return Objects.hash(1); // we have to consider "null" comparisons, so everything should go in same "bucket" + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNCompilerTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNCompilerTest.java index fcaadef883d..8b3776593ed 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNCompilerTest.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNCompilerTest.java @@ -20,10 +20,12 @@ import java.util.Arrays; import java.util.Map; +import java.util.function.Consumer; 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.DMNDecisionResult; import org.kie.dmn.api.core.DMNMessage; import org.kie.dmn.api.core.DMNModel; import org.kie.dmn.api.core.DMNResult; @@ -37,6 +39,7 @@ import org.kie.dmn.core.impl.CompositeTypeImpl; import org.kie.dmn.core.impl.DMNContextFPAImpl; import org.kie.dmn.core.impl.DMNModelImpl; +import org.kie.dmn.core.impl.DMNResultImpl; import org.kie.dmn.core.impl.SimpleTypeImpl; import org.kie.dmn.core.util.DMNRuntimeUtil; import org.kie.dmn.feel.lang.EvaluationContext; @@ -47,6 +50,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.InstanceOfAssertFactories.map; +import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.SUCCEEDED; import static org.kie.dmn.core.util.DynamicTypeUtils.entry; import static org.kie.dmn.core.util.DynamicTypeUtils.mapOf; import static org.kie.dmn.core.util.DynamicTypeUtils.prototype; @@ -164,7 +169,7 @@ void itemDefAllowedValuesString(VariantTestConf conf) { final ItemDefNode itemDef = dmnModel.getItemDefinitionByName("tEmploymentStatus" ); assertThat(itemDef.getName()).isEqualTo("tEmploymentStatus"); - assertThat(itemDef.getId()).isNull(); + assertThat(itemDef.getId()).isNotNull(); final DMNType type = itemDef.getType(); @@ -286,6 +291,57 @@ void testImport(VariantTestConf conf) { } } + @ParameterizedTest + @MethodSource("params") + void testNestedImports(VariantTestConf conf) { + testConfig = conf; + final DMNRuntime runtime = createRuntimeWithAdditionalResources("0089-nested-inputdata-imports.dmn", + this.getClass(), + "Model_B.dmn", + "Model_B2.dmn", + "Say_hello_1ID1D.dmn"); + + final DMNModel importedModelB = runtime.getModel("http://www.trisotech.com/definitions/_2a1d771a-a899-4fef-abd6-fc894332337c", + "Model B"); + assertThat(importedModelB).isNotNull(); + for (final DMNMessage message : importedModelB.getMessages()) { + LOG.debug("{}", message); + } + + final DMNModel importedModelB2 = runtime.getModel("http://www.trisotech.com/definitions/_9d46ece4-a96c-4cb0-abc0-0ca121ac3768", + "Model B2"); + assertThat(importedModelB2).isNotNull(); + for (final DMNMessage message : importedModelB2.getMessages()) { + LOG.debug("{}", message); + } + + final DMNModel importedModelA= runtime.getModel("http://www.trisotech.com/definitions/_ae5b3c17-1ac3-4e1d-b4f9-2cf861aec6d9", + "Say hello 1ID1D"); + assertThat(importedModelA).isNotNull(); + for (final DMNMessage message : importedModelA.getMessages()) { + LOG.debug("{}", message); + } + + final DMNModel modelC = runtime.getModel("http://www.trisotech.com/definitions/_10435dcd-8774-4575-a338-49dd554a0928", + "Model C"); + assertThat(modelC).isNotNull(); + for (final DMNMessage message : modelC.getMessages()) { + LOG.debug("{}", message); + } + + final DMNContext context = runtime.newContext(); + context.set("Model B", mapOf(entry("modelA", mapOf(entry("Person name", "B.A.John"))))); + context.set("Model B2", mapOf(entry("modelA", mapOf(entry("Person name", "B2.A.John2"))))); + + final DMNResult evaluateModelCDecision = runtime.evaluateByName(modelC, context, "Model C Decision based on Bs"); + for (final DMNMessage message : evaluateModelCDecision.getMessages()) { + LOG.debug("{}", message); + } + LOG.debug("{}", evaluateModelCDecision); + assertThat(evaluateModelCDecision.getDecisionResults()).size().isEqualTo(3); + evaluateModelCDecision.getDecisionResults().forEach(dmnDecisionResult -> assertThat(dmnDecisionResult.getEvaluationStatus()).isEqualTo(SUCCEEDED)); + } + @ParameterizedTest @MethodSource("params") void emptyNamedModelImportWithHrefNamespace(VariantTestConf conf) { diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNRuntimeTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNRuntimeTest.java index 54891131773..5ade865fb2f 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNRuntimeTest.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNRuntimeTest.java @@ -63,9 +63,12 @@ import org.kie.dmn.api.core.event.BeforeEvaluateDecisionTableEvent; import org.kie.dmn.api.core.event.DMNRuntimeEventListener; import org.kie.dmn.core.api.DMNFactory; +import org.kie.dmn.core.api.EvaluatorResult; import org.kie.dmn.core.ast.DMNContextEvaluator; import org.kie.dmn.core.ast.DecisionNodeImpl; +import org.kie.dmn.core.ast.EvaluatorResultImpl; import org.kie.dmn.core.impl.DMNModelImpl; +import org.kie.dmn.core.impl.SimpleTypeImpl; import org.kie.dmn.core.model.Person; import org.kie.dmn.core.util.DMNRuntimeUtil; import org.kie.dmn.core.util.KieHelper; @@ -76,9 +79,11 @@ import org.kie.dmn.feel.util.NumberEvalHelper; import org.kie.dmn.model.api.Decision; import org.kie.dmn.model.api.Definitions; +import org.kie.dmn.model.api.InformationItem; import org.kie.dmn.model.api.ItemDefinition; import org.kie.dmn.model.v1_1.TDecision; import org.kie.dmn.model.v1_1.TDefinitions; +import org.kie.dmn.model.v1_1.TInformationItem; import org.mockito.ArgumentCaptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1724,6 +1729,9 @@ private Definitions buildSimplifiedDefinitions(final String namespace, final Str for (final String d : decisions) { final Decision dec = new TDecision(); dec.setName(d); + InformationItem variable = new TInformationItem(); + variable.setName("variable-" + d); + dec.setVariable(variable); def.getDrgElement().add(dec); def.addChildren(dec); dec.setParent(def); @@ -1732,7 +1740,12 @@ private Definitions buildSimplifiedDefinitions(final String namespace, final Str } private DecisionNodeImpl buildSimplifiedDecisionNode(final Definitions def, final String name) { - return new DecisionNodeImpl(def.getDrgElement().stream().filter(drg -> drg.getName().equals(name)).filter(Decision.class::isInstance).map(Decision.class::cast).findFirst().get()); + DecisionNodeImpl toReturn = new DecisionNodeImpl(def.getDrgElement().stream().filter(drg -> drg.getName().equals(name)).filter(Decision.class::isInstance) + .map(Decision.class::cast) + .findFirst().get()); + toReturn.setEvaluator((eventManager, result) -> new EvaluatorResultImpl(name, EvaluatorResult.ResultType.SUCCESS)); + toReturn.setResultType(SimpleTypeImpl.UNKNOWN_DMNTYPE("")); + return toReturn; } @ParameterizedTest diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/impl/DMNModelImplTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/impl/DMNModelImplTest.java new file mode 100644 index 00000000000..7663612df49 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/impl/DMNModelImplTest.java @@ -0,0 +1,171 @@ +/** + * 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.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.kie.dmn.api.core.ast.DecisionNode; +import org.kie.dmn.core.ast.DecisionNodeImpl; +import org.kie.dmn.model.api.Decision; +import org.kie.dmn.model.api.Definitions; +import org.kie.dmn.model.v1_5.TDecision; +import org.kie.dmn.model.v1_5.TDefinitions; + +import static org.assertj.core.api.Assertions.assertThat; + +class DMNModelImplTest { + + private DMNModelImpl model; + private static final String MODEL_NAMESPACE = "model_namespace"; + + @BeforeEach + public void init() { + model = new DMNModelImpl(); + model.setDefinitions(getDefinitions(MODEL_NAMESPACE)); + } + + @Test + void addDecisionSameModel() { + List decisionNodeList = new ArrayList<>(); + IntStream.range(0, 3).forEach(i -> { + DecisionNode toAdd = getDecisionNode("id_" + i, "decision_" + i, model.getDefinitions()); + model.addDecision(toAdd); + decisionNodeList.add(toAdd); + }); + + decisionNodeList.forEach(decisionNode -> { + assertThat(model.getDecisionByName(decisionNode.getName())) + .isNotNull() + .isEqualTo(decisionNode); + assertThat(model.getDecisionById(decisionNode.getId())) + .isNotNull() + .isEqualTo(decisionNode); + }); + } + + @Test + void addDecisionWithoutIdSameModel() { + List decisionNodeList = new ArrayList<>(); + IntStream.range(0, 3).forEach(i -> { + DecisionNode toAdd = getDecisionNode(null, "decision_" + i, model.getDefinitions()); + model.addDecision(toAdd); + decisionNodeList.add(toAdd); + }); + + decisionNodeList.forEach(decisionNode -> + assertThat(model.getDecisionByName(decisionNode.getName())) + .isNotNull() + .isEqualTo(decisionNode)); + } + + @Test + void addDecisionWithoutNameSameModel() { + List decisionNodeList = new ArrayList<>(); + IntStream.range(0, 3).forEach(i -> { + DecisionNode toAdd = getDecisionNode("id_" + i, null, model.getDefinitions()); + model.addDecision(toAdd); + decisionNodeList.add(toAdd); + }); + decisionNodeList.forEach(decisionNode -> + assertThat(model.getDecisionById(decisionNode.getId())) + .isNotNull() + .isEqualTo(decisionNode)); + } + + @Test + void addDecisionImportedModel() { + List decisionNodeList = new ArrayList<>(); + String importedNameSpace = "imported_namespace"; + Definitions definitions = getDefinitions(importedNameSpace); + model.setImportAliasForNS(importedNameSpace, definitions.getNamespace(), definitions.getName()); + IntStream.range(0, 3).forEach(i -> { + DecisionNode toAdd = getDecisionNode("id_" + i, "decision_" + i, definitions); + model.addDecision(toAdd); + decisionNodeList.add(toAdd); + }); + + decisionNodeList.forEach(decisionNode -> { + assertThat(model.getDecisionByName(String.format("%s.%s", importedNameSpace, decisionNode.getName()))) + .isNotNull() + .isEqualTo(decisionNode); + assertThat(model.getDecisionById(String.format("%s#%s", importedNameSpace, decisionNode.getId()))) + .isNotNull() + .isEqualTo(decisionNode); + }); + } + + @Test + void addDecisionWithoutIdImportedModel() { + List decisionNodeList = new ArrayList<>(); + String importedNameSpace = "imported_namespace"; + Definitions definitions = getDefinitions(importedNameSpace); + model.setImportAliasForNS(importedNameSpace, definitions.getNamespace(), definitions.getName()); + IntStream.range(0, 3).forEach(i -> { + DecisionNode toAdd = getDecisionNode(null, "decision_" + i,definitions); + model.addDecision(toAdd); + decisionNodeList.add(toAdd); + }); + + decisionNodeList.forEach(decisionNode -> + assertThat(model.getDecisionByName(String.format("%s.%s", importedNameSpace, decisionNode.getName()))) + .isNotNull() + .isEqualTo(decisionNode)); + } + + @Test + void addDecisionWithoutNameImportedModel() { + List decisionNodeList = new ArrayList<>(); + String importedNameSpace = "imported_namespace"; + Definitions definitions = getDefinitions(importedNameSpace); + model.setImportAliasForNS(importedNameSpace, definitions.getNamespace(), definitions.getName()); + IntStream.range(0, 3).forEach(i -> { + DecisionNode toAdd = getDecisionNode("id_" + i, null, definitions); + model.addDecision(toAdd); + decisionNodeList.add(toAdd); + }); + decisionNodeList.forEach(decisionNode -> + assertThat(model.getDecisionById(String.format("%s#%s", importedNameSpace, decisionNode.getId()))) + .isNotNull() + .isEqualTo(decisionNode)); + } + + private Definitions getDefinitions(String nameSpace) { + Definitions toReturn = new TDefinitions(); + toReturn.setNamespace(nameSpace); + toReturn.setName("Definitions_" + nameSpace); + return toReturn; + } + + private DecisionNode getDecisionNode(String id, String name, Definitions parent) { + Decision decision = getDecision(id, name, parent); + return new DecisionNodeImpl(decision); + } + + private Decision getDecision(String id, String name, Definitions parent) { + Decision toReturn = new TDecision(); + toReturn.setId(id); + toReturn.setName(name); + toReturn.setParent(parent); + return toReturn; + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/impl/TupleIdentifierTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/impl/TupleIdentifierTest.java new file mode 100644 index 00000000000..af5f2eed2bc --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/impl/TupleIdentifierTest.java @@ -0,0 +1,101 @@ +/** + * 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.impl; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class TupleIdentifierTest { + + @Test + void createTupleIdentifier() { + String id = "123124"; + String name = "name"; + TupleIdentifier retrieved = TupleIdentifier.createTupleIdentifier(id, name); + assertThat(retrieved).isNotNull(); + assertThat(retrieved.getId()).isEqualTo(id); + assertThat(retrieved.getName()).isEqualTo(name); + } + + @Test + void createTupleIdentifierById() { + String id = "123124"; + TupleIdentifier retrieved = TupleIdentifier.createTupleIdentifierById(id); + assertThat(retrieved).isNotNull(); + assertThat(retrieved.getId()).isEqualTo(id); + assertThat(retrieved.getName()).isNotNull(); + } + + @Test + void createTupleIdentifierByName() { + String name = "name"; + TupleIdentifier retrieved = TupleIdentifier.createTupleIdentifierByName(name); + assertThat(retrieved).isNotNull(); + assertThat(retrieved.getName()).isEqualTo(name); + assertThat(retrieved.getId()).isNotNull(); + } + + @Test + void generateIdFromName() { + String name = "name"; + String wrongName = "wrong-name"; + String retrieved = TupleIdentifier.generateIdFromName(name); + assertThat(retrieved).isEqualTo(TupleIdentifier.generateIdFromName(name)) + .isNotEqualTo(TupleIdentifier.generateIdFromName(wrongName)); + } + + @Test + void generateNameFromId() { + String id = "123124"; + String wrongId = "423423"; + String retrieved = TupleIdentifier.generateNameFromId(id); + assertThat(retrieved).isEqualTo(TupleIdentifier.generateNameFromId(id)) + .isNotEqualTo(TupleIdentifier.generateNameFromId(wrongId)); + } + + @Test + void testTupleIdentifierEquality() { + String id = "123124"; + String wrongId = "3242342"; + String name = "name"; + String wrongName = "wrong-name"; + TupleIdentifier original = new TupleIdentifier(id, name); + commonTestEquality(original, new TupleIdentifier(id, name), true); + commonTestEquality(original, new TupleIdentifier(id, name), true); + commonTestEquality(original, new TupleIdentifier(null, name), true); + commonTestEquality(original, new TupleIdentifier(id, null), true); + + commonTestEquality(original, new TupleIdentifier(id, wrongName), false); + commonTestEquality(original, new TupleIdentifier(wrongId, name), false); + commonTestEquality(original, new TupleIdentifier(wrongId, wrongName), false); + commonTestEquality(original, new TupleIdentifier(null, wrongName), false); + commonTestEquality(original, new TupleIdentifier(wrongId, null), false); + } + + private void commonTestEquality(TupleIdentifier original, TupleIdentifier comparison, boolean shouldBeEqual) { + if (shouldBeEqual) { + assertThat(original).isEqualTo(comparison); + assertThat(comparison).isEqualTo(original); + } else { + assertThat(original).isNotEqualTo(comparison); + assertThat(comparison).isNotEqualTo(original); + } + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0089-nested-inputdata-imports.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0089-nested-inputdata-imports.dmn new file mode 100644 index 00000000000..e158a086470 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0089-nested-inputdata-imports.dmn @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + "B: " + Model B.Evaluating Say Hello + "; B2: " + Model B2.Evaluating B2 Say Hello + + + \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Model_B.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Model_B.dmn new file mode 100644 index 00000000000..9cf1c88443f --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Model_B.dmn @@ -0,0 +1,31 @@ + + + + + + + + + + "Evaluating Say Hello to: "+modelA.Greet the Person + + + diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Model_B2.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Model_B2.dmn new file mode 100644 index 00000000000..74f588ebdff --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Model_B2.dmn @@ -0,0 +1,31 @@ + + + + + + + + + + "Evaluating Say Hello to: "+modelA.Greet the Person + + + diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Say_hello_1ID1D.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Say_hello_1ID1D.dmn new file mode 100644 index 00000000000..3db63e9121d --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Say_hello_1ID1D.dmn @@ -0,0 +1,30 @@ + + + + + + + + + + + + "Hello, "+Person name + + + diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java index a7bb6c668d0..6eb78856b66 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java @@ -22,7 +22,6 @@ import java.math.MathContext; import java.time.Duration; import java.time.LocalDate; -import java.time.Period; import java.time.chrono.ChronoPeriod; import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; @@ -90,10 +89,6 @@ private Object add(Object left, Object right, EvaluationContext ctx) { if (right instanceof TemporalAmount temporalAmount) { return temporal.plus(temporalAmount); } - if (right instanceof BigDecimal bigDecimal) { - Period toAdd = Period.ofDays(bigDecimal.intValue()); - return temporal.plus(toAdd); - } } else if (left instanceof TemporalAmount temporalAmount) { if (right instanceof Temporal temporal) { return temporal.plus(temporalAmount); diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEEL12ExtendedForLoopTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEEL12ExtendedForLoopTest.java index 3b7cb5b728e..2512b4294c7 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEEL12ExtendedForLoopTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEEL12ExtendedForLoopTest.java @@ -21,6 +21,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -48,7 +49,8 @@ private static Collection data() { final Object[][] cases = new Object[][] { //normal: {"for x in [1, 2, 3] return x+1", Stream.of(1, 2, 3 ).map(x -> BigDecimal.valueOf(x + 1 ) ).collect(Collectors.toList() ), null}, - {"for x in @\"2021-01-01\"..@\"2021-01-03\" return x+1", Stream.of("2021-01-02", "2021-01-03", "2021-01-04" ).map(LocalDate::parse).collect(Collectors.toList() ), null}, + {"for x in @\"2021-01-01\"..@\"2021-01-03\" return x + @\"P1D\"", Stream.of("2021-01-02", "2021-01-03", "2021-01-04" ).map(LocalDate::parse).collect(Collectors.toList() ), null}, + {"for x in @\"2021-01-01\"..@\"2021-01-03\" return x+1", Arrays.asList(null, null, null), FEELEvent.Severity.ERROR}, //extended: {"for x in 1..3 return x+1", Stream.of(1, 2, 3).map(x -> BigDecimal.valueOf(x + 1)).collect(Collectors.toList()), null}, diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java index c22d69e572a..e011f32ba7a 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELDateTimeDurationTest.java @@ -51,6 +51,8 @@ protected void instanceTest(String expression, Object result, FEELEvent.Severity private static Collection data() { final Object[][] cases = new Object[][] { // date/time/duration function invocations + { "@\"2021-01-01\" + 10", null , FEELEvent.Severity.ERROR}, + { "@\"2021-01-01T10:10:10\" + 10", null , FEELEvent.Severity.ERROR}, { "date(\"2016-07-29\")", DateTimeFormatter.ISO_DATE.parse( "2016-07-29", LocalDate::from ) , null}, { "@\"2016-07-29\"", DateTimeFormatter.ISO_DATE.parse( "2016-07-29", LocalDate::from ) , null}, { "date(\"-0105-07-29\")", DateTimeFormatter.ISO_DATE.parse( "-0105-07-29", LocalDate::from ) , null}, // 105 BC diff --git a/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNCompilerTest.java b/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNCompilerTest.java index 0cbb307653c..51d4eb3aef7 100644 --- a/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNCompilerTest.java +++ b/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNCompilerTest.java @@ -59,7 +59,7 @@ void itemDefAllowedValuesString(VariantTestConf conf) { final ItemDefNode itemDef = dmnModel.getItemDefinitionByName("tEmploymentStatus" ); assertThat(itemDef.getName()).isEqualTo("tEmploymentStatus"); - assertThat(itemDef.getId()).isNull(); + assertThat(itemDef.getId()).isNotNull(); final DMNType type = itemDef.getType(); diff --git a/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNRuntimeTest.java b/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNRuntimeTest.java index 8c944344a45..550a7817e21 100644 --- a/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNRuntimeTest.java +++ b/kie-dmn/kie-dmn-legacy-tests/src/test/java/org/kie/dmn/legacy/tests/core/v1_1/DMNRuntimeTest.java @@ -58,9 +58,12 @@ import org.kie.dmn.api.core.event.BeforeEvaluateDecisionTableEvent; import org.kie.dmn.api.core.event.DMNRuntimeEventListener; import org.kie.dmn.core.api.DMNFactory; +import org.kie.dmn.core.api.EvaluatorResult; import org.kie.dmn.core.ast.DMNContextEvaluator; import org.kie.dmn.core.ast.DecisionNodeImpl; +import org.kie.dmn.core.ast.EvaluatorResultImpl; import org.kie.dmn.core.impl.DMNModelImpl; +import org.kie.dmn.core.impl.SimpleTypeImpl; import org.kie.dmn.core.model.Person; import org.kie.dmn.core.util.DMNRuntimeUtil; import org.kie.dmn.core.util.KieHelper; @@ -70,9 +73,11 @@ import org.kie.dmn.feel.util.NumberEvalHelper; import org.kie.dmn.model.api.Decision; import org.kie.dmn.model.api.Definitions; +import org.kie.dmn.model.api.InformationItem; import org.kie.dmn.model.api.ItemDefinition; import org.kie.dmn.model.v1_1.TDecision; import org.kie.dmn.model.v1_1.TDefinitions; +import org.kie.dmn.model.v1_1.TInformationItem; import org.mockito.ArgumentCaptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1592,6 +1597,9 @@ private Definitions buildSimplifiedDefinitions(final String namespace, final Str for (final String d : decisions) { final Decision dec = new TDecision(); dec.setName(d); + InformationItem variable = new TInformationItem(); + variable.setName("variable-" + d); + dec.setVariable(variable); def.getDrgElement().add(dec); def.addChildren(dec); dec.setParent(def); @@ -1600,7 +1608,12 @@ private Definitions buildSimplifiedDefinitions(final String namespace, final Str } private DecisionNodeImpl buildSimplifiedDecisionNode(final Definitions def, final String name) { - return new DecisionNodeImpl(def.getDrgElement().stream().filter(drg -> drg.getName().equals(name)).filter(Decision.class::isInstance).map(Decision.class::cast).findFirst().get()); + DecisionNodeImpl toReturn = new DecisionNodeImpl(def.getDrgElement().stream().filter(drg -> drg.getName().equals(name)).filter(Decision.class::isInstance) + .map(Decision.class::cast) + .findFirst().get()); + toReturn.setEvaluator((eventManager, result) -> new EvaluatorResultImpl(name, EvaluatorResult.ResultType.SUCCESS)); + toReturn.setResultType(SimpleTypeImpl.UNKNOWN_DMNTYPE("")); + return toReturn; } @ParameterizedTest(name = "{0}") diff --git a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/DMNElement.java b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/DMNElement.java index 5d32a168465..17c91a40e40 100644 --- a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/DMNElement.java +++ b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/DMNElement.java @@ -22,7 +22,7 @@ public interface DMNElement extends DMNModelInstrumentedBase { - public static interface ExtensionElements { + interface ExtensionElements { List getAny(); diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_5/ForLoopDatesEvaluate.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_5/ForLoopDatesEvaluate.dmn index fe6ab52dfc2..a1509a0d0c5 100644 --- a/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_5/ForLoopDatesEvaluate.dmn +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/valid_models/DMNv1_5/ForLoopDatesEvaluate.dmn @@ -40,7 +40,7 @@ - for x in @"2021-01-01"..@"2021-01-03" return x+1 + for x in @"2021-01-01"..@"2021-01-03" return x + duration("P1D") diff --git a/kie-dmn/uml/Compilation.png b/kie-dmn/uml/Compilation.png new file mode 100644 index 00000000000..d53645f6093 Binary files /dev/null and b/kie-dmn/uml/Compilation.png differ diff --git a/kie-dmn/uml/Compilation.puml b/kie-dmn/uml/Compilation.puml new file mode 100644 index 00000000000..aff3f0a2a90 --- /dev/null +++ b/kie-dmn/uml/Compilation.puml @@ -0,0 +1,45 @@ +/' + 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. +'/ +@startuml +participant DMNAssemblerService +DMNAssemblerService -> DMNMarshaller : unmarshal(Reader) +DMNMarshaller -> DMNAssemblerService: Definitions +DMNAssemblerService -> DMNCompilerImpl: compile(Definitions, Resource, Collection || Resource, Collection) +DMNCompilerImpl -> DMNMarshaller : unmarshal(Resource.getReader()) +DMNMarshaller -> DMNCompilerImpl: Definitions +DMNCompilerImpl -> DMNCompilerImpl: new DMNModelImpl +loop every ItemDefinition in Definitions.getItemDefinition +DMNCompilerImpl -> DMNCompilerImpl: new ItemDefNodeImpl +DMNCompilerImpl -> DMNModelImpl: addItemDefinition(ItemDefNodeImpl) +end +loop every DRGElement in Definitions.getDrgElement +DMNCompilerImpl -> DRGElementCompiler: compileNode(DRGElement, DMNCompilerImpl, DMNModel) +DRGElementCompiler -> FEELImpl: compile(String, CompilerContext) +FEELImpl -> ProcessedExpression: new +ProcessedExpression -> FEEL_1_1Parser: compilation_unit +FEEL_1_1Parser -> ProcessedExpression: ParseTree +ProcessedExpression -> ParseTree: accept(ASTBuilderVisitor) +ParseTree -> ProcessedExpression: BaseNode +FEELImpl -> ProcessedExpression: asCompiledFEELExpression +ProcessedExpression -> FEELImpl: InterpretedExecutableExpression || CompiledExecutableExpression +FEELImpl -> DRGElementCompiler: DMNExpressionEvaluator +DRGElementCompiler -> DRGElement: setEvaluator(DMNExpressionEvaluator) +end +DMNCompilerImpl -> DMNAssemblerService: DMNModel +@enduml \ No newline at end of file diff --git a/kie-dmn/uml/Execution.png b/kie-dmn/uml/Execution.png new file mode 100644 index 00000000000..626343d4b54 Binary files /dev/null and b/kie-dmn/uml/Execution.png differ diff --git a/kie-dmn/uml/Execution.puml b/kie-dmn/uml/Execution.puml new file mode 100644 index 00000000000..837af913034 --- /dev/null +++ b/kie-dmn/uml/Execution.puml @@ -0,0 +1,55 @@ +/' + 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. +'/ +@startuml +participant Actor +Actor -> DMNRuntimeUtil : createRuntime +DMNRuntimeUtil -> Actor: DMNRuntime +Actor -> DMNRuntime: getModel +DMNRuntime -> Actor : DMNModel +Actor -> DMNFactory: newContext +DMNFactory -> Actor: DMNContext +Actor -> Actor: (populate dmn context) +Actor -> DMNRuntime: evaluate*(DMNModel, DMNContext) +DMNRuntime -> DMNResultImplFactory: createResult +DMNResultImplFactory -> DMNRuntime: DMNResult +loop every DecisionNode in DMNModel.decisions +DMNRuntime -> DMNResult: getDecisionResultById +DMNResult -> DMNRuntime: DMNDecisionResultImpl +DMNRuntime -> DMNRuntime: DMNResult.setEvaluationStatus(EVALUATING) +loop every DMNNode in DecisionNode.dependencies +DMNRuntime -> DMNRuntime: DMNNode evaluate* +end +DMNRuntime -> DecisionNode: decision.getEvaluator().evaluate +DecisionNode -> DMNExpressionEvaluator: evaluate(DMNRuntimeEventManager, DMNResult) +DMNExpressionEvaluator -> FEELImpl: newEvaluationContext +FEELImpl -> DMNExpressionEvaluator: EvaluationContextImpl +DMNExpressionEvaluator -> FEELImpl: evaluate(CompiledExpression, EvaluationContextImpl) +FEELImpl -> ProcessedExpression: apply(EvaluationContextImpl) +ProcessedExpression -> CompiledFEELExpression: (InterpretedExecutableExpression || CompiledExecutableExpression) apply(EvaluationContextImpl) +CompiledFEELExpression -> ProcessedExpression: Object +ProcessedExpression -> FEELImpl: Object +FEELImpl -> DMNExpressionEvaluator: Object +DMNExpressionEvaluator -> DMNRuntime: EvaluatorResult(Object) +DMNRuntime -> CoerceUtil: coerceValue(DMNType, Object) +CoerceUtil -> DMNRuntime: Object +DMNRuntime -> DMNResult: setResult(Object) +DMNRuntime -> DMNResult: setEvaluationStatus(SUCCEEDED || FAILED) +DMNRuntime -> Actor: DMNResult +end +@enduml \ No newline at end of file