From 6281abd312c72997e306caf21b0db4fd790bed87 Mon Sep 17 00:00:00 2001 From: Gabriele Cardosi Date: Fri, 15 Mar 2024 13:57:31 +0100 Subject: [PATCH] [incubator-kie-issues#908] Validate new DMN 1.5 features (#5785) * [incubator-kie-issues#908] WIP. Moved shared models to kie-dmn-test-resources. Adapted UnnamedImportUtilsTest. Add kie-dmn-test-resources as dependency for kie-dmn-validation. Add fix for empty name import. Not working: references to unnamed imported model * [incubator-kie-issues#908] WIP. Working: references to unnamed imported model * [incubator-kie-issues#908] WIP. Implemented validation/evaluation for DateRangeLoop, listreplace, unnamed import * [incubator-kie-issues#908] Validate DMN 1.5 features. Moved shared models to kie-dmn-test-resources * [incubator-kie-issues#908] Fixed model. * [incubator-kie-issues#908] Add empty model name test --------- Co-authored-by: Gabriele-Cardosi --- .../dmn/core/compiler/UnnamedImportUtils.java | 14 +- .../org/kie/dmn/core/DMNCompilerTest.java | 4 +- .../core/compiler/UnnamedImportUtilsTest.java | 87 +++++----- .../kie/dmn/core/Imported_Model_Unamed.dmn | 69 -------- .../dmn/core/Importing_EmptyNamed_Model.dmn | 79 --------- .../kie/dmn/core/Importing_Named_Model.dmn | 79 --------- .../Importing_OverridingEmptyNamed_Model.dmn | 93 ---------- .../dmn/feel/parser/feel11/FEELParser.java | 19 +- .../AllowedValuesChecksInsideCollection.dmn | 4 +- .../resources}/DateToDateTimeFunction.dmn | 9 +- .../test/resources/ForLoopDatesEvaluate.dmn | 47 +++++ .../test/resources/Imported_Model_Unamed.dmn | 63 +++++++ .../resources/Importing_EmptyNamed_Model.dmn | 79 +++++++++ .../test/resources/Importing_Named_Model.dmn | 79 +++++++++ .../Importing_OverridingEmptyNamed_Model.dmn | 94 ++++++++++ .../test/resources/ListReplaceEvaluate.dmn | 47 +++++ .../resources/NegationOfDurationEvaluate.dmn | 41 +++++ .../test/resources/TypeConstraintsChecks.dmn | 4 +- kie-dmn/kie-dmn-validation/pom.xml | 13 ++ .../kie/dmn/validation/DMNValidatorImpl.java | 20 ++- .../DMNv1x/dmn-validation-rules.drl | 11 ++ .../org/kie/dmn/validation/ValidatorTest.java | 7 + .../validation/v1_5/DMN15ValidationsTest.java | 162 ++++++++++++++++++ .../org/kie/dmn/validation/EmptyModelName.dmn | 63 +++++++ kie-dmn/pom.xml | 2 +- 25 files changed, 809 insertions(+), 380 deletions(-) delete mode 100644 kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Imported_Model_Unamed.dmn delete mode 100644 kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_EmptyNamed_Model.dmn delete mode 100644 kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_Named_Model.dmn delete mode 100644 kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_OverridingEmptyNamed_Model.dmn rename kie-dmn/{kie-dmn-core/src/test/resources/org/kie/dmn/core => kie-dmn-test-resources/src/test/resources}/DateToDateTimeFunction.dmn (67%) create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/ForLoopDatesEvaluate.dmn create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/Imported_Model_Unamed.dmn create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_EmptyNamed_Model.dmn create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_Named_Model.dmn create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_OverridingEmptyNamed_Model.dmn create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/ListReplaceEvaluate.dmn create mode 100644 kie-dmn/kie-dmn-test-resources/src/test/resources/NegationOfDurationEvaluate.dmn create mode 100644 kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/v1_5/DMN15ValidationsTest.java create mode 100644 kie-dmn/kie-dmn-validation/src/test/resources/org/kie/dmn/validation/EmptyModelName.dmn diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/UnnamedImportUtils.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/UnnamedImportUtils.java index 3f22b334e07..1d3a963b122 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/UnnamedImportUtils.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/UnnamedImportUtils.java @@ -51,6 +51,17 @@ public static void processMergedModel(DMNModelImpl parentModel, DMNModelImpl mer // Here we try to put all the definitions from the "imported" model inside the parent one Definitions parentDefinitions = parentModel.getDefinitions(); Definitions mergedDefinitions = mergedModel.getDefinitions(); + + mergeDefinitions(parentDefinitions, mergedDefinitions); + + mergedModel.getTypeRegistry().getTypes().forEach((s, stringDMNTypeMap) -> + stringDMNTypeMap.values(). + forEach(dmnType -> parentModel.getTypeRegistry().registerType(dmnType))); + } + + public static void mergeDefinitions(Definitions parentDefinitions, Definitions mergedDefinitions) { + // incubator-kie-issues#852: The idea is to not treat the anonymous models as import, but to "merge" them with original opne, + // Here we try to put all the definitions from the "imported" model inside the parent one parentDefinitions.getArtifact().addAll(mergedDefinitions.getArtifact()); addIfNotPresent(parentDefinitions.getDecisionService(), mergedDefinitions.getDecisionService()); @@ -59,9 +70,6 @@ public static void processMergedModel(DMNModelImpl parentModel, DMNModelImpl mer addIfNotPresent(parentDefinitions.getImport(), mergedDefinitions.getImport()); addIfNotPresent(parentDefinitions.getItemDefinition(), mergedDefinitions.getItemDefinition()); mergedDefinitions.getChildren().forEach(parentDefinitions::addChildren); - mergedModel.getTypeRegistry().getTypes().forEach((s, stringDMNTypeMap) -> - stringDMNTypeMap.values(). - forEach(dmnType -> parentModel.getTypeRegistry().registerType(dmnType))); } static void addIfNotPresent(Collection target, Collection source) { 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 4f4773bfc1f..9f9617c56ac 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 @@ -278,7 +278,7 @@ public void testEmptyNamedModelImport() { this.getClass(), "Imported_Model_Unamed.dmn"); - final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df44", "Imported Model"); assertThat(importedModel).isNotNull(); for (final DMNMessage message : importedModel.getMessages()) { @@ -324,7 +324,7 @@ public void testOverridingEmptyNamedModelImport() { this.getClass(), "Imported_Model_Unamed.dmn"); - final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df44", "Imported Model"); assertThat(importedModel).isNotNull(); for (final DMNMessage message : importedModel.getMessages()) { diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/UnnamedImportUtilsTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/UnnamedImportUtilsTest.java index f92c8a1c933..24911180d0c 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/UnnamedImportUtilsTest.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/UnnamedImportUtilsTest.java @@ -1,13 +1,12 @@ package org.kie.dmn.core.compiler; -import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; -import org.drools.util.FileUtils; import org.junit.Test; import org.kie.dmn.api.core.DMNModel; import org.kie.dmn.api.core.DMNRuntime; @@ -18,7 +17,9 @@ import org.kie.dmn.model.api.NamedElement; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.kie.dmn.core.compiler.UnnamedImportUtils.addIfNotPresent; import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport; @@ -26,14 +27,12 @@ public class UnnamedImportUtilsTest { @Test public void isInUnnamedImportTrue() { - File importingModelFile = FileUtils.getFile("Importing_EmptyNamed_Model.dmn"); - assertThat(importingModelFile).isNotNull().exists(); - File importedModelFile = FileUtils.getFile("Imported_Model_Unamed.dmn"); - assertThat(importedModelFile).isNotNull().exists(); - final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources(importingModelFile, - importedModelFile); + final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources("Importing_EmptyNamed_Model" + + ".dmn", + this.getClass(), + "Imported_Model_Unamed.dmn"); - final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df44", "Imported Model"); assertThat(importedModel).isNotNull(); final DMNModelImpl importingModel = (DMNModelImpl)runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", @@ -48,14 +47,11 @@ public void isInUnnamedImportTrue() { @Test public void isInUnnamedImportFalse() { - File importingModelFile = FileUtils.getFile("Importing_Named_Model.dmn"); - assertThat(importingModelFile).isNotNull().exists(); - File importedModelFile = FileUtils.getFile("Imported_Model_Unamed.dmn"); - assertThat(importedModelFile).isNotNull().exists(); - final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources(importingModelFile, - importedModelFile); + final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources("Importing_Named_Model.dmn", + this.getClass(), + "Imported_Model_Unamed.dmn"); - final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df44", "Imported Model"); assertThat(importedModel).isNotNull(); final DMNModelImpl importingModel = (DMNModelImpl)runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", @@ -70,40 +66,43 @@ public void isInUnnamedImportFalse() { @Test public void addIfNotPresentTrue() throws IOException { - File importedModelFile = FileUtils.getFile("Imported_Model_Unamed.dmn"); - assertThat(importedModelFile).isNotNull().exists(); - - String xml = new String(Files.readAllBytes(Paths.get(importedModelFile.toURI()))); - Definitions definitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(xml); - definitions.getDecisionService().forEach(definition -> assertTrue(added(definition))); - definitions.getBusinessContextElement().forEach(definition -> assertTrue(added(definition))); - definitions.getDrgElement().forEach(definition -> assertTrue(added(definition))); - definitions.getImport().forEach(definition -> assertTrue(added(definition))); - definitions.getItemDefinition().forEach(definition -> assertTrue(added(definition))); + URL importedModelFileResource = Thread.currentThread().getContextClassLoader().getResource( + "Imported_Model_Unamed.dmn"); + assertNotNull(importedModelFileResource); + try (InputStream is = importedModelFileResource.openStream()) { + String xml = new String(is.readAllBytes(), StandardCharsets.UTF_8); + Definitions definitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(xml); + definitions.getDecisionService().forEach(definition -> assertTrue(added(definition))); + definitions.getBusinessContextElement().forEach(definition -> assertTrue(added(definition))); + definitions.getDrgElement().forEach(definition -> assertTrue(added(definition))); + definitions.getImport().forEach(definition -> assertTrue(added(definition))); + definitions.getItemDefinition().forEach(definition -> assertTrue(added(definition))); + } } @Test public void addIfNotPresentFalse() throws IOException { - File importingModelFile = FileUtils.getFile("Importing_OverridingEmptyNamed_Model.dmn"); - assertThat(importingModelFile).isNotNull().exists(); - File importedModelFile = FileUtils.getFile("Imported_Model_Unamed.dmn"); - assertThat(importedModelFile).isNotNull().exists(); - final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources(importingModelFile, - importedModelFile); - + final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources("Importing_EmptyNamed_Model.dmn", + this.getClass(), + "Imported_Model_Unamed.dmn"); final DMNModelImpl importingModel = (DMNModelImpl)runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", "Importing empty-named Model"); assertThat(importingModel).isNotNull(); Definitions importingDefinitions = importingModel.getDefinitions(); - - String importedXml = new String(Files.readAllBytes(Paths.get(importedModelFile.toURI()))); - Definitions importedDefinitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(importedXml); - importedDefinitions.getDecisionService().forEach(definition -> assertFalse(added(importingDefinitions.getDecisionService(), definition))); - importedDefinitions.getBusinessContextElement().forEach(definition -> assertFalse(added(importingDefinitions.getBusinessContextElement(), definition))); - importedDefinitions.getDrgElement().forEach(definition -> assertFalse(added(importingDefinitions.getDrgElement(), definition))); - importedDefinitions.getImport().forEach(definition -> assertFalse(added(importingDefinitions.getImport(), definition))); - importedDefinitions.getItemDefinition().forEach(definition -> assertFalse(added(importingDefinitions.getItemDefinition(), definition))); + URL importedModelFileResource = Thread.currentThread().getContextClassLoader().getResource( + "Imported_Model_Unamed.dmn"); + assertNotNull(importedModelFileResource); + try (InputStream is = importedModelFileResource.openStream()) { + String importedXml = new String(is.readAllBytes(), StandardCharsets.UTF_8); + Definitions importedDefinitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(importedXml); + importedDefinitions.getDecisionService().forEach(definition -> assertFalse(added(importingDefinitions.getDecisionService(), definition))); + importedDefinitions.getBusinessContextElement().forEach(definition -> assertFalse(added(importingDefinitions.getBusinessContextElement(), definition))); + importedDefinitions.getDrgElement().forEach(definition -> assertFalse(added(importingDefinitions.getDrgElement(), definition))); + importedDefinitions.getImport().forEach(definition -> assertFalse(added(importingDefinitions.getImport(), + definition))); + importedDefinitions.getItemDefinition().forEach(definition -> assertFalse(added(importingDefinitions.getItemDefinition(), definition))); + } } private boolean added(T source) { diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Imported_Model_Unamed.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Imported_Model_Unamed.dmn deleted file mode 100644 index d32261f9d46..00000000000 --- a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Imported_Model_Unamed.dmn +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - Say Hello( An Imported Person ) - - - - - Remote Greeting Service - - - - - - - string - - - number - - - - - - - - "Hello " + Person.name + "!" - - - - diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_EmptyNamed_Model.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_EmptyNamed_Model.dmn deleted file mode 100644 index ff2d5d27201..00000000000 --- a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_EmptyNamed_Model.dmn +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - Local Hello( A Person ) - - - - - - - - - - - - - Say Hello( A Person ) - - - - - - - - - "Local Hello " + Person.name + "!" - - - - - diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_Named_Model.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_Named_Model.dmn deleted file mode 100644 index e83912d76fc..00000000000 --- a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_Named_Model.dmn +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - Local Hello( A Person ) - - - - - - - - - - - - - Imported Model.Say Hello( A Person ) - - - - - - - - - "Local Hello " + Person.name + "!" - - - - - diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_OverridingEmptyNamed_Model.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_OverridingEmptyNamed_Model.dmn deleted file mode 100644 index 63ef659ae0c..00000000000 --- a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_OverridingEmptyNamed_Model.dmn +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - string - - - number - - - - - - - - - - - - Local Hello( A Person ) - - - - - - - - - - - - - Say Hello( A Person ) - - - - Remote Greeting Service - - - - - - - - - - "Local Hello " + Person.name + "!" - - - - - diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/FEELParser.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/FEELParser.java index ace7bcb3bd9..e78a39c2517 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/FEELParser.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/parser/feel11/FEELParser.java @@ -95,11 +95,15 @@ public static boolean isVariableNamePartValid( String namePart, Scope scope ) { return isVariableNameValid(namePart); } + public static boolean isVariableNameEmpty( String source ) { + return checkVariableNameEmpty( source ).isEmpty(); + } + public static boolean isVariableNameValid( String source ) { return checkVariableName( source ).isEmpty(); } - public static List checkVariableName( String source ) { + public static List checkVariableNameEmpty( String source ) { if( source == null || source.isEmpty() ) { return Collections.singletonList( new SyntaxErrorEvent( FEELEvent.Severity.ERROR, Msg.createMessage( Msg.INVALID_VARIABLE_NAME_EMPTY ), @@ -107,6 +111,15 @@ public static List checkVariableName( String source ) { 0, 0, null ) ); + } else { + return Collections.emptyList(); + } + } + + public static List checkVariableName( String source ) { + if( source == null || source.isEmpty() ) { + // We check validity of empty name with checkVariableNameEmpty + return Collections.emptyList(); } CharStream input = CharStreams.fromString(source); FEEL_1_1Lexer lexer = new FEEL_1_1Lexer( input ); @@ -120,8 +133,8 @@ public static List checkVariableName( String source ) { FEEL_1_1Parser.NameDefinitionWithEOFContext nameDef = parser.nameDefinitionWithEOF(); // be sure to align below parser.getRuleInvocationStack().contains("nameDefinition... if( ! errorChecker.hasErrors() && - nameDef != null && - source.trim().equals( parser.getHelper().getOriginalText( nameDef ) ) ) { + nameDef != null && + source.trim().equals( parser.getHelper().getOriginalText( nameDef ) ) ) { return Collections.emptyList(); } return errorChecker.getErrors(); diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/AllowedValuesChecksInsideCollection.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/AllowedValuesChecksInsideCollection.dmn index 81b3738abfc..fca927bc707 100644 --- a/kie-dmn/kie-dmn-test-resources/src/test/resources/AllowedValuesChecksInsideCollection.dmn +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/AllowedValuesChecksInsideCollection.dmn @@ -1,7 +1,7 @@ + diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/ForLoopDatesEvaluate.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/ForLoopDatesEvaluate.dmn new file mode 100644 index 00000000000..39b2fb4e165 --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/ForLoopDatesEvaluate.dmn @@ -0,0 +1,47 @@ + + + + + Any + + + date + + + + + + for x in @"2021-01-01"..@"2021-01-03" return x+1 + + + + + + + + 338 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/Imported_Model_Unamed.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/Imported_Model_Unamed.dmn new file mode 100644 index 00000000000..2c900040e20 --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/Imported_Model_Unamed.dmn @@ -0,0 +1,63 @@ + + + + + + + string + + + number + + + + + + + + + + + + + + + + Say Hello( An Imported Person ) + + + + + + + + + "Hello " + Person.name + "!" + + + + + diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_EmptyNamed_Model.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_EmptyNamed_Model.dmn new file mode 100644 index 00000000000..ae2fa55bdc5 --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_EmptyNamed_Model.dmn @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + Local Hello( A Person ) + + + + + + + + + + + + + Say Hello( A Person ) + + + + + + + + + "Local Hello " + Person.name + "!" + + + + + diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_Named_Model.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_Named_Model.dmn new file mode 100644 index 00000000000..cca7992ae58 --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_Named_Model.dmn @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + Local Hello( A Person ) + + + + + + + + + + + + + Imported Model.Say Hello( A Person ) + + + + + + + + + "Local Hello " + Person.name + "!" + + + + + diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_OverridingEmptyNamed_Model.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_OverridingEmptyNamed_Model.dmn new file mode 100644 index 00000000000..c75a39b44a6 --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/Importing_OverridingEmptyNamed_Model.dmn @@ -0,0 +1,94 @@ + + + + + + + + string + + + number + + + + + + + + + + + + + + + + Local Hello( A Person ) + + + + + + + + + + + + + Say Hello( A Person ) + + + + Remote Greeting Service + + + + + + + + + + "Local Hello " + Person.name + "!" + + + + + diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/ListReplaceEvaluate.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/ListReplaceEvaluate.dmn new file mode 100644 index 00000000000..cb2141e1d64 --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/ListReplaceEvaluate.dmn @@ -0,0 +1,47 @@ + + + + + Any + + + number + + + + + + list replace ( [2, 4, 7, 8], 3, 6) + + + + + + + + 338 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/NegationOfDurationEvaluate.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/NegationOfDurationEvaluate.dmn new file mode 100644 index 00000000000..b7bdcce546e --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/NegationOfDurationEvaluate.dmn @@ -0,0 +1,41 @@ + + + + + + + + -duration("P1Y6M") = (duration("P1Y6M")*-1) + + + + + + + + 338 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kie-dmn/kie-dmn-test-resources/src/test/resources/TypeConstraintsChecks.dmn b/kie-dmn/kie-dmn-test-resources/src/test/resources/TypeConstraintsChecks.dmn index f2ece77a60a..4de97f95643 100644 --- a/kie-dmn/kie-dmn-test-resources/src/test/resources/TypeConstraintsChecks.dmn +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/TypeConstraintsChecks.dmn @@ -1,7 +1,7 @@ true ${project.version} + + org.kie + kie-dmn-test-resources + ${project.version} + tests + test + @@ -133,6 +140,12 @@ provided + + org.kie + kie-dmn-test-resources + tests + test + junit junit diff --git a/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/DMNValidatorImpl.java b/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/DMNValidatorImpl.java index 2f7cfb291e2..9c7bcbb673d 100644 --- a/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/DMNValidatorImpl.java +++ b/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/DMNValidatorImpl.java @@ -73,6 +73,7 @@ import org.kie.dmn.model.api.DMNModelInstrumentedBase; import org.kie.dmn.model.api.DecisionService; import org.kie.dmn.model.api.Definitions; +import org.kie.dmn.model.api.Import; import org.kie.dmn.validation.dtanalysis.InternalDMNDTAnalyser; import org.kie.dmn.validation.dtanalysis.InternalDMNDTAnalyserFactory; import org.kie.dmn.validation.dtanalysis.model.DTAnalysis; @@ -84,6 +85,7 @@ import org.xml.sax.SAXException; import static java.util.stream.Collectors.toList; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.mergeDefinitions; import static org.kie.dmn.validation.DMNValidator.Validation.ANALYZE_DECISION_TABLE; import static org.kie.dmn.validation.DMNValidator.Validation.VALIDATE_COMPILATION; import static org.kie.dmn.validation.DMNValidator.Validation.VALIDATE_MODEL; @@ -546,7 +548,8 @@ private List validateSchema(String xml, String path) { Source s = new StreamSource(new StringReader(xml)); validateSchema(s, usingSchema); } catch (Exception e) { - problems.add(new DMNMessageImpl(DMNMessage.Severity.ERROR, MsgUtil.createMessage(Msg.FAILED_XML_VALIDATION, e.getMessage()), Msg.FAILED_XML_VALIDATION.getType(), null, e).withPath(path)); + String errorMessage = String.format("%s - %s", path, e.getMessage()); + problems.add(new DMNMessageImpl(DMNMessage.Severity.ERROR, MsgUtil.createMessage(Msg.FAILED_XML_VALIDATION, errorMessage), Msg.FAILED_XML_VALIDATION.getType(), null, e).withPath(path)); } return problems; } @@ -580,6 +583,19 @@ private void validateSchema(Source s, Schema using) throws SAXException, IOExcep private List validateModel(DMNResource mainModel, List otherModels) { Definitions mainDefinitions = mainModel.getDefinitions(); + List unnamedImports = mainDefinitions.getImport().stream() + .filter(anImport -> anImport.getName() == null || anImport.getName().isEmpty()) + .map(Import::getNamespace) + .toList(); + List otherModelsDefinitions = new ArrayList<>(); + otherModels.forEach(dmnResource -> { + Definitions other = dmnResource.getDefinitions(); + if (unnamedImports.contains(dmnResource.getModelID().getNamespaceURI())) { + mergeDefinitions(mainDefinitions, other); + } + otherModelsDefinitions.add(other); + }); + StatelessKieSession kieSession = mainDefinitions instanceof org.kie.dmn.model.v1_1.KieDMNModelInstrumentedBase ? kb11.newStatelessKieSession() : kb12.newStatelessKieSession(); MessageReporter reporter = new MessageReporter(mainModel); kieSession.setGlobal( "reporter", reporter ); @@ -589,7 +605,7 @@ private List validateModel(DMNResource mainModel, List .filter(d -> !(d instanceof DecisionService && Boolean.parseBoolean(d.getAdditionalAttributes().get(new QName("http://www.trisotech.com/2015/triso/modeling", "dynamicDecisionService"))))) .collect(toList()); - List otherModelsDefinitions = otherModels.stream().map(DMNResource::getDefinitions).collect(Collectors.toList()); + BatchExecutionCommand batch = CommandFactory.newBatchExecution(Arrays.asList(CommandFactory.newInsertElements(dmnModelElements, "DEFAULT", false, "DEFAULT"), CommandFactory.newInsertElements(otherModelsDefinitions, "DMNImports", false, "DMNImports"))); kieSession.execute(batch); diff --git a/kie-dmn/kie-dmn-validation/src/main/resources/org/kie/dmn/validation/DMNv1x/dmn-validation-rules.drl b/kie-dmn/kie-dmn-validation/src/main/resources/org/kie/dmn/validation/DMNv1x/dmn-validation-rules.drl index 5d584f588bb..565c916b21e 100644 --- a/kie-dmn/kie-dmn-validation/src/main/resources/org/kie/dmn/validation/DMNv1x/dmn-validation-rules.drl +++ b/kie-dmn/kie-dmn-validation/src/main/resources/org/kie/dmn/validation/DMNv1x/dmn-validation-rules.drl @@ -68,6 +68,17 @@ then $b.getParentDRDElement().getIdentifierString() ); end +rule NAME_EMPTY +when + $ne : NamedElement( !(this instanceof Import), + !FEELParser.isVariableNameEmpty( name ) ) +then + java.util.List errors = FEELParser.checkVariableNameEmpty( $ne.getName() ); + if ( ! errors.isEmpty() ) { + reporter.report( DMNMessage.Severity.ERROR, $ne , Msg.INVALID_NAME, $ne.getName(), errors.get( 0 ).getMessage() ); + } +end + rule NAME_INVALID when $ne : NamedElement( parent != null, diff --git a/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/ValidatorTest.java b/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/ValidatorTest.java index c3ce0937720..f19151aa408 100644 --- a/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/ValidatorTest.java +++ b/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/ValidatorTest.java @@ -138,6 +138,13 @@ public void testNAME_INVALID_empty_name() { assertThat(validate.stream().anyMatch(p -> p.getMessageType().equals(DMNMessageType.INVALID_NAME) && p.getSourceId().equals("_5e43b55c-888e-443c-b1b9-80e4aa6746bd"))).isTrue(); assertThat(validate.stream().anyMatch(p -> p.getMessageType().equals(DMNMessageType.INVALID_NAME) && p.getSourceId().equals("_b1e4588e-9ce1-4474-8e4e-48dbcdb7524b"))).isTrue(); } + + @Test + public void testNAME_EMPTY_empty_model_name() { + List validate = validator.validate( getReader( "EmptyModelName.dmn" ), VALIDATE_SCHEMA, VALIDATE_MODEL, VALIDATE_COMPILATION); + assertThat(validate).withFailMessage(ValidatorUtil.formatMessages(validate)).hasSize(1); + assertThat(validate.stream().anyMatch(p -> p.getMessageType().equals(DMNMessageType.INVALID_NAME) && p.getSourceId().equals("_f27bb64b-6fc7-4e1f-9848-11ba35e0df44"))).isTrue(); + } @Test public void testDRGELEM_NOT_UNIQUE() { diff --git a/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/v1_5/DMN15ValidationsTest.java b/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/v1_5/DMN15ValidationsTest.java new file mode 100644 index 00000000000..90326b19a95 --- /dev/null +++ b/kie-dmn/kie-dmn-validation/src/test/java/org/kie/dmn/validation/v1_5/DMN15ValidationsTest.java @@ -0,0 +1,162 @@ +/** + * 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.validation.v1_5; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.drools.io.ClassPathResource; +import org.junit.Test; +import org.kie.api.io.Resource; +import org.kie.dmn.api.core.DMNContext; +import org.kie.dmn.api.core.DMNMessage; +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.compiler.profiles.ExtendedDMNProfile; +import org.kie.dmn.core.util.DMNRuntimeUtil; +import org.kie.dmn.validation.DMNValidator; +import org.kie.dmn.validation.DMNValidatorFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.kie.dmn.core.util.DynamicTypeUtils.entry; +import static org.kie.dmn.core.util.DynamicTypeUtils.prototype; + +public class DMN15ValidationsTest { + + public static final Logger LOG = LoggerFactory.getLogger(DMN15ValidationsTest.class); + + static final DMNValidator validator = DMNValidatorFactory.newValidator(List.of(new ExtendedDMNProfile())); + static final DMNValidator.ValidatorBuilder validatorBuilder = validator.validateUsing(DMNValidator.Validation.VALIDATE_SCHEMA, DMNValidator.Validation.VALIDATE_MODEL); + + + @Test + public void overridingUnnamedImportValidation() { + String importedModelFileName = "Imported_Model_Unamed.dmn"; + String importingModelFileName = "Importing_OverridingEmptyNamed_Model.dmn"; + String modelName = "Importing empty-named Model"; + String modelNamespace = "http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc"; + validate(importedModelFileName, importingModelFileName); + Map inputData = Map.of("A Person", prototype(entry("name", "Hugh"), entry("age", 32))); + evaluate(modelNamespace, modelName, importingModelFileName, inputData, importedModelFileName); + } + + @Test + public void namedImportValidation() { + String importedModelFileName = "Imported_Model_Unamed.dmn"; + String importingModelFileName = "Importing_Named_Model.dmn"; + String modelName = "Importing named Model"; + String modelNamespace = "http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc"; + validate(importedModelFileName, importingModelFileName); + Map inputData = Map.of("A Person", prototype(entry("name", "Hugh"), entry("age", 32))); + evaluate(modelNamespace, modelName, importingModelFileName, inputData, importedModelFileName); + } + + @Test + public void unnamedImportValidation() { + String importedModelFileName = "Imported_Model_Unamed.dmn"; + String importingModelFileName = "Importing_EmptyNamed_Model.dmn"; + String modelName = "Importing empty-named Model"; + String modelNamespace = "http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc"; + validate(importedModelFileName, importingModelFileName); + Map inputData = Map.of("A Person", prototype(entry("name", "Hugh"), entry("age", 32))); + evaluate(modelNamespace, modelName, importingModelFileName, inputData, importedModelFileName); + } + + @Test + public void forLoopDatesEvaluateValidation() { + String modelFileName = "ForLoopDatesEvaluate.dmn"; + String modelName = "For Loop Dates Evaluate"; + String modelNamespace = "http://www.trisotech.com/dmn/definitions/_09E8A38A-AD24-4C3D-8307-029C0C4D373F"; + validate(modelFileName); + evaluate(modelNamespace, modelName, modelFileName, Collections.EMPTY_MAP); + } + + @Test + public void listReplaceEvaluateValidation() { + String modelFileName = "ListReplaceEvaluate.dmn"; + String modelName = "List Replace Evaluate"; + String modelNamespace = "http://www.trisotech.com/dmn/definitions/_09E8A38A-AD24-4C3D-8307-029C0C4D373F"; + validate(modelFileName); + evaluate(modelNamespace, modelName, modelFileName, Collections.EMPTY_MAP); + } + + @Test + public void negationOfDurationEvaluateValidation() { + String modelFileName = "NegationOfDurationEvaluate.dmn"; + String modelName = "Negation of Duration Evaluate"; + String modelNamespace = "http://www.trisotech.com/dmn/definitions/_09E8A38A-AD24-4C3D-8307-029C0C4D373F"; + validate(modelFileName); + evaluate(modelNamespace, modelName, modelFileName, Collections.EMPTY_MAP); + } + + @Test + public void dateToDateTimeFunctionValidation() { + String modelFileName = "DateToDateTimeFunction.dmn"; + String modelName = "new-file"; + String modelNamespace = "https://kiegroup.org/dmn/_A7F17D7B-F0AB-4C0B-B521-02EA26C2FBEE"; + validate(modelFileName); + evaluate(modelNamespace, modelName, modelFileName, Collections.EMPTY_MAP); + } + + @Test + public void typeConstraintsChecksValidation() { + String modelFileName = "TypeConstraintsChecks.dmn"; + String modelName = "TypeConstraintsChecks"; + String modelNamespace = "http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442"; + validate(modelFileName); + Map inputData = Map.of("p1", prototype(entry("Name", "P1"), entry("Interests", Collections.singletonList("Golf")))); + evaluate(modelNamespace, modelName, modelFileName, inputData); + } + + private void validate(String modelFileName, String... otherFileNames) { + List allModelsFileNames = new ArrayList<>(); + allModelsFileNames.add(modelFileName); + allModelsFileNames.addAll(List.of(otherFileNames)); + Resource[] resources = allModelsFileNames.stream() + .map(fileName -> new ClassPathResource(fileName, + this.getClass())) + .toArray(value -> new Resource[allModelsFileNames.size()]); + List dmnMessages = validatorBuilder.theseModels(resources); + assertNotNull(dmnMessages); + dmnMessages.forEach(dmnMessage -> LOG.error(dmnMessage.toString())); + assertTrue(dmnMessages.isEmpty()); + } + + private void evaluate(String modelNamespace, String modelName, String modelFileName, + Map inputData, String... otherFileNames) { + final DMNRuntime runtime = DMNRuntimeUtil.createRuntimeWithAdditionalResources(modelFileName, + this.getClass(), + otherFileNames); + final DMNModel dmnModel = runtime.getModel(modelNamespace, + modelName); + assertThat(dmnModel).isNotNull(); + assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse(); + final DMNContext ctx = runtime.newContext(); + inputData.forEach(ctx::set); + DMNResult toReturn = runtime.evaluateAll(dmnModel, ctx); + assertThat(toReturn.hasErrors()).as(DMNRuntimeUtil.formatMessages(toReturn.getMessages())).isFalse(); + } +} diff --git a/kie-dmn/kie-dmn-validation/src/test/resources/org/kie/dmn/validation/EmptyModelName.dmn b/kie-dmn/kie-dmn-validation/src/test/resources/org/kie/dmn/validation/EmptyModelName.dmn new file mode 100644 index 00000000000..69a239b2676 --- /dev/null +++ b/kie-dmn/kie-dmn-validation/src/test/resources/org/kie/dmn/validation/EmptyModelName.dmn @@ -0,0 +1,63 @@ + + + + + + + string + + + number + + + + + + + + + + + + + + + + Say Hello( An Imported Person ) + + + + + + + + + "Hello " + Person.name + "!" + + + + + diff --git a/kie-dmn/pom.xml b/kie-dmn/pom.xml index 1b7c2de2912..b875a0445d6 100644 --- a/kie-dmn/pom.xml +++ b/kie-dmn/pom.xml @@ -39,6 +39,7 @@ kie-dmn-api kie-dmn-model + kie-dmn-test-resources kie-dmn-feel kie-dmn-core kie-dmn-backend @@ -54,7 +55,6 @@ kie-dmn-core-jsr223-jq kie-dmn-core-jsr223 kie-dmn-ruleset2dmn-parent - kie-dmn-test-resources