diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 4ca61fe604d..bda8c4e565f 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -74,7 +74,7 @@ 0.18.0 4.7.4 1.11.1 - 3.2.9.Final + 3.2.10.Final 3.4.0 8.5.11 4.13.1 @@ -90,7 +90,7 @@ 1.7.3 3.5.3 2.0.28 - 5.1.0 + 5.2.5 10.1.7 3.23.1 3.33.0 @@ -98,11 +98,11 @@ 4.0.4 1.3 - 6.2.13.Final 2.3.0 14.0.13.Final 3.26.0-GA 6.0.1.Final + 3.5.3.Final 7.6.1.Final 3.1.6.Final 3.0.3 @@ -121,8 +121,9 @@ 6.0.0 4.0.0 3.0.0 - 1.1.2 + 1.1.5 2.1.2 + 4.0.0 1.6.4 5.9.3 2.5.1.Final @@ -171,7 +172,7 @@ 3.7.1 0.9.1 3.3.4 - 1.24.0 + 1.26.0 1.3 @@ -202,7 +203,7 @@ 42.6.0 2.0.1 - 3.1.2 + 3.1.6 3.0.3 3.25.3 @@ -375,11 +376,6 @@ xstream ${version.com.thoughtworks.xstream} - - com.thoughtworks.xstream - xstream-hibernate - ${version.com.thoughtworks.xstream} - guru.nidi @@ -777,23 +773,6 @@ ${version.org.hamcrest} - - org.hibernate.orm - hibernate-envers - ${version.org.hibernate} - - - org.hibernate.orm - hibernate-jpamodelgen - ${version.org.hibernate} - - - javax.xml.bind - jaxb-api - - - - org.javassist javassist @@ -1099,29 +1078,6 @@ test - - org.hibernate.orm - hibernate-core - ${version.org.hibernate} - - - javax.persistence - javax.persistence-api - - - org.jboss.spec.javax.transaction - jboss-transaction-api_1.2_spec - - - javax.activation - javax.activation-api - - - javax.xml.bind - jaxb-api - - - jakarta.validation @@ -1288,6 +1244,17 @@ microprofile-config-api ${version.org.eclipse.microprofile.config} + + + org.apache.openjpa + openjpa + ${version.org.apache.openjpa} + + + org.jboss.logging + jboss-logging + ${version.org.jboss.logging} + diff --git a/drools-commands/src/main/resources/META-INF/native-image/reflect-config.json b/drools-commands/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000000..0e7203cac09 --- /dev/null +++ b/drools-commands/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,245 @@ +[ + { + "name" : "org.drools.commands.runtime.BatchExecutionCommandImpl", + "allDeclaredConstructors" : true, + "allPublicConstructors" : true, + "allDeclaredMethods" : true, + "allPublicMethods" : true, + "allDeclaredFields" : true, + "allPublicFields" : true + }, + { + "name": "org.drools.commands.runtime.process.AbortWorkItemCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.process.SignalEventCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.process.StartProcessCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.DeleteCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.GetGlobalCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.SetGlobalCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.InsertElementsCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.QueryCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.InsertObjectCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.ModifyCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.ModifyCommand$SetterImpl", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.UpdateCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.GetObjectCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.FireAllRulesCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.FireUntilHaltCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.DisposeCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.process.CompleteWorkItemCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.GetObjectsCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.AgendaGroupSetFocusCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.ClearActivationGroupCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.ClearAgendaCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.ClearAgendaGroupCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.ClearRuleFlowGroupCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.rule.GetFactHandlesCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.pmml.ApplyPmmlModelCommand", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.commands.runtime.ExecutionResultImpl", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/ClasspathKieProject.java b/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/ClasspathKieProject.java index 42c9d6659af..7d9a7aa9b6f 100644 --- a/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/ClasspathKieProject.java +++ b/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/ClasspathKieProject.java @@ -30,6 +30,8 @@ import java.net.URI; import java.net.URL; import java.net.URLDecoder; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -416,11 +418,9 @@ public static String fixURLFromKProjectPath(URL url) { } try { - urlPath = URLDecoder.decode( urlPath, - "UTF-8" ); + urlPath = URLDecoder.decode( urlPath, "UTF-8" ); } catch ( UnsupportedEncodingException e ) { - throw new IllegalArgumentException( "Error decoding URL (" + url + ") using UTF-8", - e ); + throw new IllegalArgumentException( "Error decoding URL (" + url + ") using UTF-8", e ); } log.debug("KieModule URL type=" + urlType + " url=" + urlPath); @@ -429,38 +429,47 @@ public static String fixURLFromKProjectPath(URL url) { } private static String getPathForVFS(URL url) { - Method m = null; + Method vfsGetPhysicalFileMethod = null; try { - m = Class.forName("org.jboss.vfs.VirtualFile").getMethod("getPhysicalFile"); + vfsGetPhysicalFileMethod = Class.forName("org.jboss.vfs.VirtualFile").getMethod("getPhysicalFile"); } catch (Exception e) { try { // Try to retrieve the VirtualFile class also on TCCL - m = Class.forName("org.jboss.vfs.VirtualFile", true, Thread.currentThread().getContextClassLoader()).getMethod("getPhysicalFile"); + vfsGetPhysicalFileMethod = Class.forName("org.jboss.vfs.VirtualFile", true, Thread.currentThread().getContextClassLoader()).getMethod("getPhysicalFile"); } catch (Exception e1) { // VirtualFile is not available on the classpath - ignore log.warn( "Found virtual file " + url + " but org.jboss.vfs.VirtualFile is not available on the classpath" ); } } - Method m2 = null; + + Class vfsClass = null; + Method vfsGetChildMethod = null; + boolean useTccl = false; + try { - m2 = Class.forName("org.jboss.vfs.VFS").getMethod("getChild", URI.class); + vfsClass = lookupVfsClass("org.jboss.vfs.VFS", useTccl); + vfsGetChildMethod = Class.forName("org.jboss.vfs.VFS").getMethod("getChild", URI.class); } catch (Exception e) { try { // Try to retrieve the org.jboss.vfs.VFS class also on TCCL - m2 = Class.forName("org.jboss.vfs.VFS", true, Thread.currentThread().getContextClassLoader()).getMethod("getChild", URI.class); + useTccl = true; + vfsClass = lookupVfsClass("org.jboss.vfs.VFS", useTccl); + vfsGetChildMethod = vfsClass.getMethod("getChild", URI.class); } catch (Exception e1) { // VFS is not available on the classpath - ignore log.warn( "Found virtual file " + url + " but org.jboss.vfs.VFS is not available on the classpath" ); } } - if (m == null || m2 == null) { + if (vfsGetPhysicalFileMethod == null || vfsGetChildMethod == null) { return url.getPath(); } String path = null; + Object virtualFile = null; try { - File f = (File)m.invoke( m2.invoke(null, url.toURI()) ); + virtualFile = vfsGetChildMethod.invoke( null, url.toURI() ); + File f = (File) vfsGetPhysicalFileMethod.invoke( virtualFile ); path = PortablePath.of(f.getPath()).asString(); } catch (Exception e) { log.error( "Error when reading virtual file from " + url.toString(), e ); @@ -475,18 +484,11 @@ private static String getPathForVFS(URL url) { return path; } - int kModulePos = urlString.length() - ("/" + KieModuleModelImpl.KMODULE_JAR_PATH.asString()).length(); - boolean isInJar = urlString.startsWith(".jar", kModulePos - 4); - try { - if (isInJar && path.contains("contents/")) { - String jarName = urlString.substring(0, kModulePos); - jarName = jarName.substring(jarName.lastIndexOf('/')+1); - String jarFolderPath = path.substring( 0, path.length() - ("contents/" + KieModuleModelImpl.KMODULE_JAR_PATH.asString()).length() ); - String jarPath = jarFolderPath + jarName; - path = new File(jarPath).exists() ? jarPath : jarFolderPath + "content"; - } else if (path.endsWith("/" + KieModuleModelImpl.KMODULE_FILE_NAME)) { - path = path.substring( 0, path.length() - ("/" + KieModuleModelImpl.KMODULE_JAR_PATH.asString()).length() ); + path = rewriteVFSPath(path, urlString); + if (!Files.exists(Paths.get(path))) { + // see https://issues.redhat.com/browse/DROOLS-7608 + path = rewriteVFSPathAfter_7_4_15(vfsClass, virtualFile, useTccl); } log.info( "Virtual file physical path = " + path ); @@ -497,6 +499,41 @@ private static String getPathForVFS(URL url) { return url.getPath(); } + private static String rewriteVFSPath(String path, String urlString) { + int kModulePos = urlString.length() - ("/" + KieModuleModelImpl.KMODULE_JAR_PATH.asString()).length(); + boolean isInJar = urlString.startsWith(".jar", kModulePos - 4); + + if (isInJar && path.contains("contents/")) { + String jarName = urlString.substring(0, kModulePos); + jarName = jarName.substring(jarName.lastIndexOf('/')+1); + String jarFolderPath = path.substring( 0, path.length() - ("contents/" + KieModuleModelImpl.KMODULE_JAR_PATH.asString()).length() ); + String jarPath = jarFolderPath + jarName; + path = new File(jarPath).exists() ? jarPath : jarFolderPath + "content"; + } + if (path.endsWith("/" + KieModuleModelImpl.KMODULE_FILE_NAME)) { + return path.substring( 0, path.length() - ("/" + KieModuleModelImpl.KMODULE_JAR_PATH.asString()).length() ); + } + return path; + } + + private static String rewriteVFSPathAfter_7_4_15(Class vfsClass, Object virtualFile, boolean useTccl) throws Exception { + Method vfsGetMountMethod = vfsClass.getDeclaredMethod("getMount", lookupVfsClass("org.jboss.vfs.VirtualFile", useTccl)); + vfsGetMountMethod.setAccessible(true); + Object mount = vfsGetMountMethod.invoke(null, virtualFile); + + Method mountGetFileSystemMethod = lookupVfsClass("org.jboss.vfs.VFS$Mount", useTccl).getDeclaredMethod("getFileSystem"); + mountGetFileSystemMethod.setAccessible(true); + Object fileSystem = mountGetFileSystemMethod.invoke(mount); + + Method fileSystemGetMountSourceMethod = lookupVfsClass("org.jboss.vfs.spi.FileSystem", useTccl).getMethod("getMountSource"); + File mountSource = (File) fileSystemGetMountSourceMethod.invoke(fileSystem); + return mountSource.getPath(); + } + + private static Class lookupVfsClass(String classname, boolean useTccl) throws ClassNotFoundException { + return useTccl ? Class.forName(classname, true, Thread.currentThread().getContextClassLoader()) : Class.forName(classname); + } + public InternalKieModule getKieModuleForKBase(String kBaseName) { return this.kJarFromKBaseName.get( kBaseName ); } diff --git a/drools-core/src/main/java/org/drools/core/phreak/PhreakBuilder.java b/drools-core/src/main/java/org/drools/core/phreak/PhreakBuilder.java index b2db030c71a..578015f7e47 100644 --- a/drools-core/src/main/java/org/drools/core/phreak/PhreakBuilder.java +++ b/drools-core/src/main/java/org/drools/core/phreak/PhreakBuilder.java @@ -40,7 +40,7 @@ static boolean isEagerSegmentCreation() { void removeRule(TerminalNode tn, Collection wms, InternalRuleBase kBase); class Holder { - private static final boolean EAGER_SEGMENT_CREATION = Boolean.parseBoolean(getConfig("drools.useEagerSegmentCreation", "false")); + private static final boolean EAGER_SEGMENT_CREATION = Boolean.parseBoolean(getConfig("drools.useEagerSegmentCreation", "true")); private static final PhreakBuilder PHREAK_BUILDER = EAGER_SEGMENT_CREATION ? new EagerPhreakBuilder() : new LazyPhreakBuilder(); } } diff --git a/drools-core/src/main/java/org/drools/core/reteoo/Rete.java b/drools-core/src/main/java/org/drools/core/reteoo/Rete.java index 45c4b0fe36e..64382068e8c 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/Rete.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/Rete.java @@ -19,7 +19,6 @@ package org.drools.core.reteoo; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,13 +55,9 @@ */ public class Rete extends ObjectSource implements ObjectSink { + private final Map entryPoints = new HashMap<>(); - - private static final long serialVersionUID = 510l; - - private Map entryPoints; - - private transient InternalRuleBase kBase; + private final InternalRuleBase kBase; public Rete() { this( null ); @@ -74,9 +69,7 @@ public Rete() { public Rete(InternalRuleBase kBase) { super( 0, RuleBasePartitionId.MAIN_PARTITION ); - this.entryPoints = Collections.synchronizedMap( new HashMap<>() ); this.kBase = kBase; - hashcode = calculateHashCode(); } diff --git a/drools-core/src/main/resources/META-INF/native-image/reflect-config.json b/drools-core/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000000..003cc246bf1 --- /dev/null +++ b/drools-core/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,47 @@ +[ + { + "name": "org.drools.core.base.RuleNameEndsWithAgendaFilter", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.core.base.RuleNameStartsWithAgendaFilter", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.core.base.RuleNameEqualsAgendaFilter", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.core.base.RuleNameMatchesAgendaFilter", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "org.drools.core.runtime.rule.impl.DefaultConsequenceExceptionHandler", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_KIEBuilding-section.adoc b/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_KIEBuilding-section.adoc index 2a65195fa1e..d3b4ffd3eb0 100644 --- a/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_KIEBuilding-section.adoc +++ b/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_KIEBuilding-section.adoc @@ -453,7 +453,7 @@ The `KieResources` provides many convenient factory methods to convert an ``Inpu .KieResources image::KIE/BuildDeployUtilizeAndRun/KieResources.png[align="center"] -NOTE: `URLResource` is no longer available with Drools 8 series, to ensure a more reproducible knowledge base build (avoiding remote URLs) and for better security. If you used `URLResource` in the past, you can manage the local-fetch of the remote resource externally to your Drools application, so to limit Drools build concerns to local resources containing knowledge assets. +NOTE: `URLResource` is no longer available since Drools 8, to ensure a more reproducible knowledge base build (avoiding remote URLs) and for better security. If you used `URLResource` in the past, you can manage the local-fetch of the remote resource externally to your Drools application, so to limit Drools build concerns to local resources containing knowledge assets. Normally the type of a `Resource` can be inferred from the extension of the name used to add it to the ``KieFileSystem``. However it also possible to not follow the Kie conventions about file extensions and explicitly assign a specific `ResourceType` to a `Resource` as shown below: diff --git a/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-api.adoc b/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-api.adoc index 341493adcf3..ad2e2856438 100644 --- a/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-api.adoc +++ b/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-api.adoc @@ -2,7 +2,7 @@ = Rule Unit API -Previous sections in this chapter explained traditional KIE APIs to use {RULE_ENGINE}. However, as introduced in xref:getting-started/index.adoc#first-rule-project_getting-started[First Rule Project], Rule Unit is a new and recommended style for implementing rules in {PRODUCT} 8. +Previous sections in this chapter explained traditional KIE APIs to use {RULE_ENGINE}. However, as introduced in xref:getting-started/index.adoc#first-rule-project_getting-started[First Rule Project], Rule Unit is a new and recommended style for implementing rules since {PRODUCT} 8. A rule unit is an atomic module defining a set of rules and a set of strongly typed *data sources* through which the facts processed by the rules are inserted. The *data sources* of 2 kinds: `DataStream` and `DataStore` which will be described later in this section. diff --git a/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-dsl.adoc b/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-dsl.adoc index 5f2d397d3c0..405ae60f21d 100644 --- a/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-dsl.adoc +++ b/drools-docs/src/modules/ROOT/pages/KIE/BuildDeployUtilizeAndRun/_rule-unit-dsl.adoc @@ -2,7 +2,7 @@ = Rule Unit DSL -In addition to the standard Rule Unit API, {PRODUCT} 8 offers an alternative way of writing rules in combination with Rule Unit. +In addition to the standard Rule Unit API, {PRODUCT} offers an alternative way of writing rules in combination with Rule Unit. You can now define the rules for Rule Units using a dedicated set of Java APIs. TIP: As an end-user, you can think of the Rule Unit DSL as an alternative to DRL, in order to define your rules with an internal DSL using Java. diff --git a/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes-drools.adoc b/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes-drools.adoc index d9c6ce473d0..68c3f5edad3 100644 --- a/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes-drools.adoc +++ b/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes-drools.adoc @@ -1,22 +1,9 @@ -// Do NOT delete repeated or superfluous variables unless the same can be deleted from all other attributes docs (for DM, PAM, jBPM, etc.). All attributes here are in use in product docs at this time, and as we single source, we need those same variables to render appropriately for Drools. But do please correct and add info where necessary. (Stetson, 2 Aug 2018) - :PRODUCT: Drools -:PRODUCT_SHORT: Drools :PRODUCT_INIT: drools -:PRODUCT_INIT_CAP: DROOLS -:PRODUCT_INIT_BA: drools -:PRODUCT_INIT_CAP_BA: DROOLS -:PRODUCT_DROOLS: Drools - -:PRODUCT_OLD: Drools -:URL_COMPONENT_PRODUCT_OLD: drools :PRODUCT_VERSION: {COMMUNITY_VERSION} :PRODUCT_VERSION_LONG: {COMMUNITY_VERSION_LONG} :PRODUCT_FILE: {PRODUCT_INIT}-{COMMUNITY_VERSION_LONG} -:PRODUCT_FILE_BA: {PRODUCT_INIT_BA}-{COMMUNITY_VERSION_LONG} - -:URL_COMPONENT_PRODUCT: drools :RULE_ENGINE: Drools rule engine diff --git a/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes.adoc b/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes.adoc index f77761a2009..b53ca7c2e2f 100644 --- a/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes.adoc +++ b/drools-docs/src/modules/ROOT/pages/_artifacts/document-attributes.adoc @@ -1,17 +1,7 @@ - -:REBUILT: Tuesday, April 05, 2022 - - -:ENTERPRISE_VERSION: 7.12 -:ENTERPRISE_VERSION_LONG: 7.12.0 -:COMMUNITY_VERSION: 7.59 -:COMMUNITY_VERSION_LONG: 7.59.0 +:COMMUNITY_VERSION: 10.0 +:COMMUNITY_VERSION_LONG: 10.0.0 :COMMUNITY_VERSION_FINAL: {COMMUNITY_VERSION_LONG}.Final -:COMMUNITY_VERSION_BRANCH: 7.59.x -:ENTERPRISE_VERSION_SHORT: 712 - -// For copyright -:YEAR: 2022 +:COMMUNITY_VERSION_BRANCH: 10.0.x ifdef::DROOLS,JBPM,OP[] :MAVEN_ARTIFACT_VERSION: {COMMUNITY_VERSION_FINAL} diff --git a/drools-docs/src/modules/ROOT/pages/getting-started/_first-rule-project.adoc b/drools-docs/src/modules/ROOT/pages/getting-started/_first-rule-project.adoc index 5b729135bac..1b66f452aee 100644 --- a/drools-docs/src/modules/ROOT/pages/getting-started/_first-rule-project.adoc +++ b/drools-docs/src/modules/ROOT/pages/getting-started/_first-rule-project.adoc @@ -11,7 +11,7 @@ This guide walks you through the process of creating a simple Drools application == Creating a project with maven archetype -You can choose a style of rule project from Rule Unit or traditional style. Rule Unit is a new style that is recommended for microservices and cloud native applications. Traditional style is the same as Drools 7. Both styles are supported in Drools 8. +You can choose a style of rule project from Rule Unit or traditional style. Rule Unit is a new style that is recommended for microservices and cloud native applications. The traditional style has been used since Drools 7. Both styles are supported in Drools 10. === Rule Unit style 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 07aefbce971..33aa0bd0147 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 @@ -54,7 +54,7 @@ Each rule must have a unique name within the rule unit. If you use the same rule [id="new-and-traditional-syntax_{context}"] == New and traditional syntax -In Drools 8, we promote a new DRL syntax based on rule unit, data source, and OOPath. Hence, you will see such a new syntax in DRL syntax examples. However, Drools 8 still fully supports traditional DRL syntax that has been used in version 7 and prior. +In Drools 10, we promote a new DRL syntax based on rule unit, data source, and OOPath. Hence, you will see such a new syntax in DRL syntax examples. However, Drools 10 still fully supports traditional DRL syntax that has been used in prior versions. .Example rule for loan application with traditional syntax [source] @@ -144,6 +144,11 @@ rule "Using a rule unit with a declared type" end ---- +[NOTE] +==== +If you declare a rule unit in DRL, the rule unit class will be generated by {PRODUCT} at build time. It means that you can't refer to the class in your client codes. Declared rule units are only useful when the client is also auto-generated, for example, REST endpoint generation by Drools-Quarkus integration. +==== + To separate the fact types from the rule unit for use with other DRL rules, you can declare the types in a separate DRL file and then use the DRL rule file to declare the data sources by using the `RuleUnitData` interface implementation: .Example DRL type declaration as a separate file diff --git a/drools-docs/src/modules/ROOT/pages/migration-guide/_missing_features_components.adoc b/drools-docs/src/modules/ROOT/pages/migration-guide/_missing_features_components.adoc index 7db762abcbe..d4ffd569003 100644 --- a/drools-docs/src/modules/ROOT/pages/migration-guide/_missing_features_components.adoc +++ b/drools-docs/src/modules/ROOT/pages/migration-guide/_missing_features_components.adoc @@ -3,7 +3,7 @@ [id='kie-server_{context}'] == {KIE_SERVER} -{KIE_SERVER} is retired and no longer a component of {PRODUCT} 8. If your system is working on {KIE_SERVER} with {PRODUCT} 7, consider migration to https://kogito.kie.org/[{KOGITO}]. +{KIE_SERVER} is retired and no longer a component since {PRODUCT} 8. If your system is working on {KIE_SERVER} with {PRODUCT} 7, consider migration to https://kogito.kie.org/[{KOGITO}]. While {KIE_SERVER} hosts multiple kjar containers, One {KOGITO} instance hosts one domain service. Hence, you would create {KOGITO} project per kjar container, which would be microservice style. @@ -12,7 +12,7 @@ You can find the detailed migration steps in the next sections, xref:con-migrati [id='business-central_{context}'] == {BUSINESS_CENTRAL} -{BUSINESS_CENTRAL} is retired and no longer a component of {PRODUCT} 8. For asset management, the usual version control system is recommended, for example, git. For editors, https://marketplace.visualstudio.com/items?itemName=kie-group.vscode-extension-kogito-bundle[VS Code Kogito extension] is recommended. +{BUSINESS_CENTRAL} is retired and no longer a component since {PRODUCT} 8. For asset management, the usual version control system is recommended, for example, git. For editors, https://marketplace.visualstudio.com/items?itemName=kie-group.vscode-extension-kogito-bundle[VS Code Kogito extension] is recommended. [id='bpmn-integration_{context}'] == BPMN integration / Rule flow diff --git a/drools-docs/src/modules/ROOT/pages/migration-guide/_traditional-to-ruleunit.adoc b/drools-docs/src/modules/ROOT/pages/migration-guide/_traditional-to-ruleunit.adoc index 086ff068b42..9b916576100 100644 --- a/drools-docs/src/modules/ROOT/pages/migration-guide/_traditional-to-ruleunit.adoc +++ b/drools-docs/src/modules/ROOT/pages/migration-guide/_traditional-to-ruleunit.adoc @@ -1,10 +1,12 @@ [id='traditional-to-ruleunit_{context}'] -= Migrate to {PRODUCT} 8 += Migrate to {PRODUCT} 10 -This guide explains how to migrate {PRODUCT} 7 projects to {PRODUCT} 8 projects. +Firstly, migration from {PRODUCT} 8 to {PRODUCT} 10 is very simple. You just need to change the version in `pom.xml`. All APIs and DRL syntax are compatible. -Firstly, {PRODUCT} 8 supports {PRODUCT} 7 APIs and DRL syntax, so basically you don't need to change your Java codes and rule assets. However, there are some cautions in `pom.xml`. +This guide rather focuses on how to migrate from {PRODUCT} 7 projects to {PRODUCT} 10 projects. + +{PRODUCT} 10 supports {PRODUCT} 7 APIs and DRL syntax, so basically you don't need to change your Java codes and rule assets. However, there are some cautions in `pom.xml`. Typical {PRODUCT} 6 or early 7 projects have pom dependencies like this. @@ -36,11 +38,11 @@ But since {PRODUCT} 7.45.0, `drools-engine` and `drools-engine-classic` have bee ---- -{PRODUCT} 8 made some module refactoring, so you may find some difficulty in collecting dependencies. This aggregator dependency would help. +Some module refactoring were made during the version upgrades, so you may find some difficulty in collecting dependencies. This aggregator dependency would help. == Rule Unit -As introduced in xref:getting-started/index.adoc#first-rule-project_getting-started[First Rule Project], using Rule Units is a recommended style for implementing rules in {PRODUCT} 8. It will require some modifications to your codebase, but if you are going to develop cloud-native applications, Rule Units are strongly recommended, because {KOGITO} also works with Rule Units. +As introduced in xref:getting-started/index.adoc#first-rule-project_getting-started[First Rule Project], using Rule Units is a recommended style for implementing rules in {PRODUCT} 10. It will require some modifications to your codebase, but if you are going to develop cloud-native applications, Rule Units are strongly recommended, because {KOGITO} also works with Rule Units. .Rule Unit pom.xml [xml,subs=attributes+] diff --git a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-10.0.adoc b/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-10.0.adoc new file mode 100644 index 00000000000..5a8a793a1e4 --- /dev/null +++ b/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-10.0.adoc @@ -0,0 +1,5 @@ +== 10.0.0 release notes + +=== PLACE_HOLDER + +PLACE_HOLDER \ No newline at end of file diff --git a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.31.adoc b/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.31.adoc deleted file mode 100644 index b6c4bf6f3c6..00000000000 --- a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.31.adoc +++ /dev/null @@ -1,5 +0,0 @@ -== 8.31.0.Final release notes - -=== Security Manager support is removed - -Following the JDK Security Manager deprecation for removal in Java 17, since version 8.31.0.Final {PRODUCT} no longer allows to define a Security Manager for constraints evaluation and consequences execution. \ No newline at end of file diff --git a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.32.adoc b/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.32.adoc deleted file mode 100644 index 62c2196ba3d..00000000000 --- a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.32.adoc +++ /dev/null @@ -1,5 +0,0 @@ -== 8.32.0.Final release notes - -=== 'with' statement is retired - -'with' statement is no longer supported in DRL. It should be easily rewritten with valid Java syntax. \ No newline at end of file diff --git a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.33.adoc b/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.33.adoc deleted file mode 100644 index d19d6041bab..00000000000 --- a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.33.adoc +++ /dev/null @@ -1,5 +0,0 @@ -== 8.33.0.Final release notes - -=== `drools-engine` or `drools-ruleunits-engine` dependency is enough to generate the executable model - -In order to generate the executable model with `kie-maven-plugin`, `drools-model-compiler` was required in prior versions, but starting from {PRODUCT} 8.33, `drools-engine` or `drools-ruleunits-engine` dependency is enough to generate the executable model. \ No newline at end of file diff --git a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.40.adoc b/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.40.adoc deleted file mode 100644 index 652e9d61e67..00000000000 --- a/drools-docs/src/modules/ROOT/pages/release-notes/_release-notes-8.40.adoc +++ /dev/null @@ -1,7 +0,0 @@ -== 8.40.0.Final release notes - -=== `kie-maven-plugin` update with maven compiler release - -In order to make the `kie-maven-plugin` compatible with Maven-based project using a more recent version of the JDK, the plugin will now also look at the `maven.compiler.release` property to infer the language level to use during the build, with precedence over the `java.version` property (old behaviour). - -When the `kie-maven-plugin` is used in a Maven-based project, we recommend NOT to set the `drools.dialect.java.compiler.lnglevel` in your KJAR project, so that the version of the JDK to be used is the one fully governed by the whole Maven build (including Java source files). diff --git a/drools-docs/src/modules/ROOT/pages/release-notes/index.adoc b/drools-docs/src/modules/ROOT/pages/release-notes/index.adoc index aecf038023a..e7f73660f0c 100644 --- a/drools-docs/src/modules/ROOT/pages/release-notes/index.adoc +++ b/drools-docs/src/modules/ROOT/pages/release-notes/index.adoc @@ -7,59 +7,21 @@ include::../_artifacts/document-attributes.adoc[] [id='{context}'] = Release Notes -This chapter contains the release notes for the {PRODUCT} 8-series. +This chapter contains the release notes for the {PRODUCT} 10-series. -For the release notes of the _previous releases_, you can refer to the {PRODUCT} documentation of link:https://docs.drools.org/7.73.0.Final/drools-docs/html_single/index.html#droolsreleasenoteschapter[version 7]. +For the release notes of the _previous releases_, you can refer to the {PRODUCT} documentation of link:https://docs.drools.org/8.44.0.Final/drools-docs/drools/release-notes/index.html[version 8]. -include::_release-notes-8.40.adoc[leveloffset=0] +// This is a placeholder for adding the release notes per minor version +// include::_release-notes-10.0.adoc[leveloffset=0] -include::_release-notes-8.33.adoc[leveloffset=0] - -include::_release-notes-8.32.adoc[leveloffset=0] - -include::_release-notes-8.31.adoc[leveloffset=0] - -== Drools 8-series release notes - -Drools 8-series is a natural evolution of the 7.x-stream, incorporating many features and lessons learned integrating with Kogito and many cloud-native use cases. - -=== Rule Unit - -Rule Unit is a central paradigm in the Drools 8-series. -You can refer the xref:migration-guide/index.adoc[Migration guide chapter] for more information about Rule Unit. +== Drools 10-series release notes === Minimum requirements update -JDK 11 is now the minimum Java version required to compile Drools and make use of Drools. +JDK 17 is now the minimum Java version required to compile Drools and make use of Drools. -Maven `3.8.6` is now the minimum Maven version required to build Drools from source, or using `kie-ci` APIs. +Maven `3.8.6` is the minimum Maven version required to build Drools from source, or using `kie-ci` APIs. === `drools-mvel` and `drools-engine-classic` are deprecated -`drools-mvel` is now deprecated in favor of the executable model. It also means `drools-engine` is recommended instead of deprecated `drools-engine-classic` that contains `drools-mvel`. You can still use MVEL syntax in your rules, so it doesn't affect how to write rules. - -=== Spreadsheet decision tables file extensions - -All the files with `.xls`, `.xlsx` and `.csv` extensions were considered and parsed as decision tables. In general, this was a too strong assumption, breaking projects containing random excel files that are not decision tables and generating artificial compile-time errors. To prevent such problems the filename extension policy has been changed, considering and processing files as decision tables only if their extension is prepended with `.drl`. In other words, **only** files with `.drl.xls`, `.drl.xlsx`, and `.drl.csv` extensions will now be compiled as decision tables. For example: `rules.drl.xls` is a valid extension and will be included for rule compilation when found in a KJAR based project. - -=== Security Manager deprecation - -The {PRODUCT} features related to the JDK Security Manager will not be further developed, as starting with JDK17, the Java Platform has deprecated the Security Manager for removal. -You can refer the xref:KIE/index.adoc#_securitymanager[Security Manager chapter] for more information. - -=== KIE Marshallers notice - -The `KieMarshallers` are still present and working as expected on the release of the Drools 8-series, however we are currently exploring alternative solutions more idiomatic to cloud-native use cases. - -=== {KIE_SERVER} is retired -{KIE_SERVER} is no longer a component of {PRODUCT} 8. For migration, see xref:migration-guide/index.adoc#kie-server_migration-guide[Migration guide chapter]. - -=== {BUSINESS_CENTRAL} is retired -{BUSINESS_CENTRAL} is no longer a component of {PRODUCT} 8. For migration, see xref:migration-guide/index.adoc#business-central_migration-guide[Migration guide chapter]. - -=== OSGi support is retired -{PRODUCT} 8 no longer provides out of the box OSGi support in its modules. - -== Previous release notes - -For the release notes of the previous releases, you can refer the {PRODUCT} documentation of link:https://docs.drools.org/7.73.0.Final/drools-docs/html_single/index.html#droolsreleasenoteschapter[version 7]. \ No newline at end of file +`drools-mvel` is deprecated since Drools 8 in favor of the executable model. It also means drools-engine` is recommended instead of deprecated `drools-engine-classic` that contains `drools-mvel`. You can still use MVEL syntax in your rules, so it doesn't affect how to write rules. \ No newline at end of file diff --git a/drools-model/drools-codegen-common/pom.xml b/drools-model/drools-codegen-common/pom.xml index 52a5bea8ddf..a0888da9179 100644 --- a/drools-model/drools-codegen-common/pom.xml +++ b/drools-model/drools-codegen-common/pom.xml @@ -30,7 +30,6 @@ 999-SNAPSHOT - org.drools drools-codegen-common Drools :: Codegen :: Common @@ -44,5 +43,20 @@ com.github.javaparser javaparser-core + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + \ No newline at end of file diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java index 37479b76a1c..2e2bab4d361 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java @@ -47,6 +47,7 @@ public static AppPaths.BuildTool findBuildTool() { private final boolean isJar; private final BuildTool bt; private final Path resourcesPath; + private final Path generatedResourcesPath; private final Path outputTarget; public static AppPaths fromProjectDir(Path projectDir, Path outputTarget) { @@ -79,8 +80,10 @@ protected AppPaths(Set projectPaths, Collection classesPaths, boolea this.outputTarget = outputTarget; if (bt == BuildTool.GRADLE) { resourcesPath = Paths.get(""); // no prefix required + generatedResourcesPath = null; } else { resourcesPath = Paths.get("src", resourcesBasePath, "resources"); + generatedResourcesPath = Paths.get(TARGET_DIR, "generated-resources"); } } @@ -106,11 +109,27 @@ private Path[] getJarPaths() { } public File[] getResourceFiles() { - return projectPaths.stream().map(p -> p.resolve(resourcesPath).toFile()).toArray(File[]::new); + File[] toReturn = projectPaths.stream().map(p -> p.resolve(resourcesPath).toFile()).toArray(File[]::new); + if (generatedResourcesPath != null) { + File[] generatedResourcesFiles = projectPaths.stream().map(p -> p.resolve(generatedResourcesPath).toFile()).toArray(File[]::new); + File[] newToReturn = new File[toReturn.length + generatedResourcesFiles.length]; + System.arraycopy( toReturn, 0, newToReturn, 0, toReturn.length ); + System.arraycopy( generatedResourcesFiles, 0, newToReturn, toReturn.length, generatedResourcesFiles.length ); + toReturn = newToReturn; + } + return toReturn; } public Path[] getResourcePaths() { - return transformPaths(projectPaths, p -> p.resolve(resourcesPath)); + Path[] toReturn = transformPaths(projectPaths, p -> p.resolve(resourcesPath)); + if (generatedResourcesPath != null) { + Path[] generatedResourcesPaths = transformPaths(projectPaths, p -> p.resolve(generatedResourcesPath)); + Path[] newToReturn = new Path[toReturn.length + generatedResourcesPaths.length]; + System.arraycopy( toReturn, 0, newToReturn, 0, toReturn.length ); + System.arraycopy( generatedResourcesPaths, 0, newToReturn, toReturn.length, generatedResourcesPaths.length ); + toReturn = newToReturn; + } + return toReturn; } public Path[] getSourcePaths() { diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFile.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFile.java index bd5455641e2..741754efd30 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFile.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFile.java @@ -67,7 +67,7 @@ private GeneratedFile(GeneratedFileType type, Path path, String pathAsString, by } this.path = path; - this.pathAsString = pathAsString; + this.pathAsString = pathAsString.replace('\\', '/'); this.contents = contents; } diff --git a/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java b/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java new file mode 100644 index 00000000000..a13c03a86ce --- /dev/null +++ b/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java @@ -0,0 +1,134 @@ +package org.drools.codegen.common; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; + +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.drools.codegen.common.AppPaths.TARGET_DIR; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Execution(ExecutionMode.SAME_THREAD) +public class AppPathsTest { + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void fromProjectDir(boolean withGradle) { + String projectDirPath = "projectDir"; + String outputTargetPath = "outputTarget"; + Path projectDir = Path.of(projectDirPath); + Path outputTarget = Path.of(outputTargetPath); + if (withGradle) { + System.setProperty("org.gradle.appname", "gradle-impl"); + } else { + System.clearProperty("org.gradle.appname"); + } + AppPaths retrieved = AppPaths.fromProjectDir(projectDir, outputTarget); + getPathsTest(retrieved, projectDirPath, withGradle, false); + getFirstProjectPathTest(retrieved, projectDirPath, outputTargetPath, withGradle); + getResourceFilesTest(retrieved, projectDirPath, withGradle, false); + getResourcePathsTest(retrieved, projectDirPath, withGradle, false); + getSourcePathsTest(retrieved, projectDirPath); + getClassesPathsTest(retrieved); + getOutputTargetTest(retrieved, outputTargetPath); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void fromTestDir(boolean withGradle) { + String projectDirPath = "projectDir"; + String outputTargetPath = String.format("%s/%s", projectDirPath, TARGET_DIR).replace("/", File.separator); + Path projectDir = Path.of(projectDirPath); + if (withGradle) { + System.setProperty("org.gradle.appname", "gradle-impl"); + } else { + System.clearProperty("org.gradle.appname"); + } + AppPaths retrieved = AppPaths.fromTestDir(projectDir); + getPathsTest(retrieved, projectDirPath, withGradle, true); + getFirstProjectPathTest(retrieved, projectDirPath, outputTargetPath, withGradle); + getResourceFilesTest(retrieved, projectDirPath, withGradle, true); + getResourcePathsTest(retrieved, projectDirPath, withGradle, true); + getSourcePathsTest(retrieved, projectDirPath); + getClassesPathsTest(retrieved); + getOutputTargetTest(retrieved, outputTargetPath); + } + + private void getPathsTest(AppPaths toCheck, String projectDirPath, boolean isGradle, boolean isTestDir) { + Path[] retrieved = toCheck.getPaths(); + commonCheckResourcePaths(retrieved, projectDirPath, isGradle, isTestDir,"getPathsTest"); + } + + private void getFirstProjectPathTest(AppPaths toCheck, String projectPath, String outputPath, boolean isGradle) { + Path retrieved = toCheck.getFirstProjectPath(); + String expectedPath; + if (isGradle) { + expectedPath = outputPath; + } else { + expectedPath = projectPath; + } + assertEquals(Path.of(expectedPath), retrieved, "AppPathsTest.getFirstProjectPathTest"); + } + + private void getResourceFilesTest(AppPaths toCheck, String projectDirPath, boolean isGradle, boolean isTestDir) { + File[] retrieved = toCheck.getResourceFiles(); + int expected = isGradle ? 1 : 2; + assertEquals(expected, retrieved.length, "AppPathsTest.getResourceFilesTest"); + String expectedPath; + String sourceDir = isTestDir ? "test" : "main"; + if (isGradle) { + expectedPath = projectDirPath; + } else { + expectedPath = String.format("%s/src/%s/resources", projectDirPath, sourceDir).replace("/", File.separator); + } + assertEquals(new File(expectedPath), retrieved[0], "AppPathsTest.getResourceFilesTest"); + if (!isGradle) { + expectedPath = String.format("%s/target/generated-resources", projectDirPath).replace("/", File.separator); + assertEquals(new File(expectedPath), retrieved[1], "AppPathsTest.getResourceFilesTest"); + } + } + + private void getResourcePathsTest(AppPaths toCheck, String projectDirPath, boolean isGradle, boolean isTestDir) { + Path[] retrieved = toCheck.getResourcePaths(); + commonCheckResourcePaths(retrieved, projectDirPath, isGradle, isTestDir, "getResourcePathsTest"); + } + + private void getSourcePathsTest(AppPaths toCheck, String projectPath) { + Path[] retrieved = toCheck.getSourcePaths(); + assertEquals(1, retrieved.length, "AppPathsTest.getSourcePathsTest"); + String expectedPath = String.format("%s/src", projectPath).replace("/", File.separator); + assertEquals(Path.of(expectedPath), retrieved[0], "AppPathsTest.getSourcePathsTest"); + } + + private void getClassesPathsTest(AppPaths toCheck) { + Collection retrieved = toCheck.getClassesPaths(); + assertTrue(retrieved.isEmpty(), "AppPathsTest.getClassesPathsTest"); + } + + private void getOutputTargetTest(AppPaths toCheck, String outputPath) { + Path retrieved = toCheck.getOutputTarget(); + assertEquals(Path.of(outputPath), retrieved, "AppPathsTest.getOutputTargetTest"); + } + + private void commonCheckResourcePaths(Path[] toCheck, String projectDirPath, boolean isGradle, boolean isTestDir, String methodName) { + int expected = isGradle ? 1 : 2; + assertEquals(expected, toCheck.length, String.format("AppPathsTest.%s", methodName)); + String expectedPath; + String sourceDir = isTestDir ? "test" : "main"; + if (isGradle) { + expectedPath = projectDirPath; + } else { + expectedPath = String.format("%s/src/%s/resources", projectDirPath, sourceDir).replace("/", File.separator); + } + assertEquals(Path.of(expectedPath), toCheck[0], String.format("AppPathsTest.%s", methodName)); + if (!isGradle) { + expectedPath = String.format("%s/target/generated-resources", projectDirPath).replace("/", File.separator); + assertEquals(Path.of(expectedPath), toCheck[1], String.format("AppPathsTest.%s", methodName)); + } + } +} \ No newline at end of file diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/OOPathExprGenerator.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/OOPathExprGenerator.java index e16ba61bcf6..ca6882f98c8 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/OOPathExprGenerator.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/OOPathExprGenerator.java @@ -199,7 +199,7 @@ private void toPatternExpr(String bindingId, List list, DrlxPar expr.setScope( patternExpr ); patternExpr = expr; } - if (singleDrlx.getExpr() != null && !(singleDrlx.getExpr() instanceof NameExpr)) { + if (singleDrlx.getExpr() != null && singleDrlx.isPredicate()) { MethodCallExpr expr = expressionBuilder.buildExpressionWithIndexing( singleDrlx ); expr.setScope( patternExpr ); patternExpr = expr; diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java index 8702e687c90..f8a7365708c 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java @@ -202,7 +202,12 @@ private void logWarnIfNoReactOnCausedByVariableFromDifferentPattern(DrlxParseRes private void addDeclaration(DrlxExpression drlx, SingleDrlxParseSuccess singleResult, String bindId) { TypedDeclarationSpec decl = context.addDeclaration(bindId, getDeclarationType(drlx, singleResult)); if (drlx.getExpr() instanceof NameExpr) { - decl.setBoundVariable( PrintUtil.printNode(drlx.getExpr()) ); + decl.setBoundVariable(PrintUtil.printNode(drlx.getExpr())); + } else if (drlx.getExpr() instanceof EnclosedExpr && drlx.getBind() != null) { + ExpressionTyperContext expressionTyperContext = new ExpressionTyperContext(); + ExpressionTyper expressionTyper = new ExpressionTyper(context, singleResult.getPatternType(), bindId, false, expressionTyperContext); + TypedExpressionResult typedExpressionResult = expressionTyper.toTypedExpression(drlx.getExpr()); + singleResult.setBoundExpr(typedExpressionResult.typedExpressionOrException()); } else if (drlx.getExpr() instanceof BinaryExpr) { Expression leftMostExpression = getLeftMostExpression(drlx.getExpr().asBinaryExpr()); decl.setBoundVariable(PrintUtil.printNode(leftMostExpression)); @@ -212,7 +217,7 @@ private void addDeclaration(DrlxExpression drlx, SingleDrlxParseSuccess singleRe ExpressionTyper expressionTyper = new ExpressionTyper(context, singleResult.getPatternType(), bindId, false, expressionTyperContext); TypedExpressionResult leftTypedExpressionResult = expressionTyper.toTypedExpression(leftMostExpression); Optional optLeft = leftTypedExpressionResult.getTypedExpression(); - if (!optLeft.isPresent()) { + if (optLeft.isEmpty()) { throw new IllegalStateException("Cannot create TypedExpression for " + drlx.getExpr().asBinaryExpr().getLeft()); } singleResult.setBoundExpr(optLeft.get()); @@ -501,21 +506,23 @@ private DrlxParseResult parseNameExpr(DrlNameExpr nameExpr, Class patternType Expression withThis = DrlxParseUtil.prepend(new NameExpr(THIS_PLACEHOLDER), converted.getExpression()); if (hasBind) { - return new SingleDrlxParseSuccess(patternType, bindingId, null, converted.getType() ) + return new SingleDrlxParseSuccess(patternType, bindingId, withThis, converted.getType() ) .setLeft( new TypedExpression( withThis, converted.getType() ) ) .addReactOnProperty( lcFirstForBean(nameExpr.getNameAsString()) ); - } else if (context.hasDeclaration( expression )) { + } + + if (context.hasDeclaration( expression )) { Optional declarationSpec = context.getTypedDeclarationById(expression); if (declarationSpec.isPresent()) { return new SingleDrlxParseSuccess(patternType, bindingId, context.getVarExpr(printNode(drlxExpr)), declarationSpec.get().getDeclarationClass() ).setIsPredicate(true); } else { throw new IllegalArgumentException("Cannot find declaration specification by specified expression " + expression + "!"); } - } else { - return new SingleDrlxParseSuccess(patternType, bindingId, withThis, converted.getType() ) - .addReactOnProperty( nameExpr.getNameAsString() ) - .setIsPredicate(true); } + + return new SingleDrlxParseSuccess(patternType, bindingId, withThis, converted.getType() ) + .addReactOnProperty( nameExpr.getNameAsString() ) + .setIsPredicate(true); } private DrlxParseResult parseFieldAccessExpr( FieldAccessExpr fieldCallExpr, Class patternType, String bindingId ) { diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ArithmeticCoercedExpression.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/NumberAndStringArithmeticOperationCoercion.java similarity index 66% rename from drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ArithmeticCoercedExpression.java rename to drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/NumberAndStringArithmeticOperationCoercion.java index 765a6b313e2..82d7c8c8542 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ArithmeticCoercedExpression.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/NumberAndStringArithmeticOperationCoercion.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import com.github.javaparser.ast.expr.BinaryExpr.Operator; @@ -28,6 +29,7 @@ import com.github.javaparser.ast.expr.NameExpr; import org.drools.model.codegen.execmodel.errors.InvalidExpressionErrorResult; import org.drools.model.codegen.execmodel.generator.TypedExpression; +import org.drools.util.Pair; import static com.github.javaparser.ast.NodeList.nodeList; import static com.github.javaparser.ast.expr.BinaryExpr.Operator.DIVIDE; @@ -38,35 +40,44 @@ import static java.util.Arrays.asList; import static org.drools.util.ClassUtils.isNumericClass; -public class ArithmeticCoercedExpression { +public final class NumberAndStringArithmeticOperationCoercion { - private final TypedExpression left; - private final TypedExpression right; - private final Operator operator; + private NumberAndStringArithmeticOperationCoercion() { + } private static final Set arithmeticOperators = new HashSet<>(asList(PLUS, MINUS, MULTIPLY, DIVIDE, REMAINDER)); - public ArithmeticCoercedExpression(TypedExpression left, TypedExpression right, Operator operator) { - this.left = left; - this.right = right; - this.operator = operator; + public static Pair coerceIfNeeded(final Operator operator, final TypedExpression left, final TypedExpression right) { + if (requiresCoercion(operator, left, right)) { + return coerce(operator, left, right); + } else { + return new Pair<>(null, null); + } + } + + public static boolean requiresCoercion(final Operator operator, final TypedExpression left, final TypedExpression right) { + if (!arithmeticOperators.contains(operator)) { + return false; + } + return canCoerce(left.getRawClass(), right.getRawClass()); + } + + private static boolean canCoerce(Class leftClass, Class rightClass) { + return leftClass == String.class && isNumericClass(rightClass) || + rightClass == String.class && isNumericClass(leftClass); } /* * This coercion only deals with String vs Numeric types. * BigDecimal arithmetic operation is handled by ExpressionTyper.convertArithmeticBinaryToMethodCall() */ - public ArithmeticCoercedExpressionResult coerce() { - - if (!requiresCoercion()) { - return new ArithmeticCoercedExpressionResult(left, right); // do not coerce - } + private static Pair coerce(final Operator operator, final TypedExpression left, final TypedExpression right) { final Class leftClass = left.getRawClass(); final Class rightClass = right.getRawClass(); if (!canCoerce(leftClass, rightClass)) { - throw new ArithmeticCoercedExpressionException(new InvalidExpressionErrorResult("Arithmetic operation requires compatible types. Found " + leftClass + " and " + rightClass)); + throw new NumberAndStringArithmeticOperationCoercionException(new InvalidExpressionErrorResult("Arithmetic operation requires compatible types. Found " + leftClass + " and " + rightClass)); } TypedExpression coercedLeft = left; @@ -90,65 +101,26 @@ public ArithmeticCoercedExpressionResult coerce() { } } - return new ArithmeticCoercedExpressionResult(coercedLeft, coercedRight); + return new Pair<>(coercedLeft, coercedRight); } - private boolean requiresCoercion() { - if (!arithmeticOperators.contains(operator)) { - return false; - } - final Class leftClass = left.getRawClass(); - final Class rightClass = right.getRawClass(); - if (leftClass == rightClass) { - return false; - } - if (isNumericClass(leftClass) && isNumericClass(rightClass)) { - return false; - } - return true; - } - - private boolean canCoerce(Class leftClass, Class rightClass) { - return leftClass == String.class && isNumericClass(rightClass) || - rightClass == String.class && isNumericClass(leftClass); - } - - private TypedExpression coerceToDouble(TypedExpression typedExpression) { + private static TypedExpression coerceToDouble(TypedExpression typedExpression) { final Expression expression = typedExpression.getExpression(); TypedExpression coercedExpression = typedExpression.cloneWithNewExpression(new MethodCallExpr(new NameExpr("Double"), "valueOf", nodeList(expression))); return coercedExpression.setType(BigDecimal.class); } - private TypedExpression coerceToString(TypedExpression typedExpression) { + private static TypedExpression coerceToString(TypedExpression typedExpression) { final Expression expression = typedExpression.getExpression(); TypedExpression coercedExpression = typedExpression.cloneWithNewExpression(new MethodCallExpr(new NameExpr("String"), "valueOf", nodeList(expression))); return coercedExpression.setType(String.class); } - public static class ArithmeticCoercedExpressionResult { - - private final TypedExpression coercedLeft; - private final TypedExpression coercedRight; - - public ArithmeticCoercedExpressionResult(TypedExpression left, TypedExpression coercedRight) { - this.coercedLeft = left; - this.coercedRight = coercedRight; - } - - public TypedExpression getCoercedLeft() { - return coercedLeft; - } - - public TypedExpression getCoercedRight() { - return coercedRight; - } - } - - public static class ArithmeticCoercedExpressionException extends RuntimeException { + public static class NumberAndStringArithmeticOperationCoercionException extends RuntimeException { private final transient InvalidExpressionErrorResult invalidExpressionErrorResult; - ArithmeticCoercedExpressionException(InvalidExpressionErrorResult invalidExpressionErrorResult) { + NumberAndStringArithmeticOperationCoercionException(InvalidExpressionErrorResult invalidExpressionErrorResult) { this.invalidExpressionErrorResult = invalidExpressionErrorResult; } diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SingleDrlxParseSuccess.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SingleDrlxParseSuccess.java index d0557abdb54..fa85133a62d 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SingleDrlxParseSuccess.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SingleDrlxParseSuccess.java @@ -357,8 +357,26 @@ public boolean isPredicate() { return this.isPredicate; } + /* + * This method finds out, if the parse result is a predicate enclosed in parentheses, bound to a variable. + * Example: Person($booleanVariable: (name != null)) + * This shouldn't apply to any other form of predicate. So e.g. + * Person($booleanVariable: (name != null) == "someName") should be properly generated as a constraint. + * After discussions, to align the executable model behaviour with the old non-executable model, + * such predicate is not generated as a rule constraint, and just bound to a variable. This behaviour needs more + * discussions to revisit this behaviour. + */ + private boolean isEnclosedPredicateBoundToVariable() { + final TypedExpression boundExpr = getBoundExpr(); + return boundExpr != null + && boundExpr.getExpression() instanceof EnclosedExpr + && getExprBinding() != null + && !getLeft().getExpression().equals(boundExpr.getExpression()) + && !getRight().getExpression().equals(boundExpr.getExpression()); + } + public SingleDrlxParseSuccess setIsPredicate(boolean predicate) { - this.isPredicate = predicate; + this.isPredicate = predicate && !isEnclosedPredicateBoundToVariable(); return this; } diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expression/AbstractExpressionBuilder.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expression/AbstractExpressionBuilder.java index 8c071c0d43c..1bdf5bcde53 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expression/AbstractExpressionBuilder.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expression/AbstractExpressionBuilder.java @@ -115,8 +115,17 @@ protected Expression getBindingExpression(SingleDrlxParseSuccess drlxParseResult } else { final TypedExpression boundExpr = drlxParseResult.getBoundExpr(); // Can we unify it? Sometimes expression is in the left sometimes in expression - final Expression e = boundExpr != null ? findLeftmostExpression(boundExpr.getExpression()) : drlxParseResult.getExpr(); - return buildConstraintExpression(drlxParseResult, drlxParseResult.getUsedDeclarationsOnLeft(), e); + final Expression expression; + if (boundExpr != null) { + if (boundExpr.getExpression() instanceof EnclosedExpr) { + expression = boundExpr.getExpression(); + } else { + expression = findLeftmostExpression(boundExpr.getExpression()); + } + } else { + expression = drlxParseResult.getExpr(); + } + return buildConstraintExpression(drlxParseResult, drlxParseResult.getUsedDeclarationsOnLeft(), expression); } } diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java index 2aad35aba30..f95c6037920 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java @@ -44,6 +44,7 @@ import com.github.javaparser.ast.expr.CastExpr; import com.github.javaparser.ast.expr.CharLiteralExpr; import com.github.javaparser.ast.expr.ClassExpr; +import com.github.javaparser.ast.expr.ConditionalExpr; import com.github.javaparser.ast.expr.DoubleLiteralExpr; import com.github.javaparser.ast.expr.EnclosedExpr; import com.github.javaparser.ast.expr.Expression; @@ -71,7 +72,7 @@ import org.drools.model.codegen.execmodel.generator.RuleContext; import org.drools.model.codegen.execmodel.generator.TypedExpression; import org.drools.model.codegen.execmodel.generator.UnificationTypedExpression; -import org.drools.model.codegen.execmodel.generator.drlxparse.ArithmeticCoercedExpression; +import org.drools.model.codegen.execmodel.generator.drlxparse.NumberAndStringArithmeticOperationCoercion; import org.drools.model.codegen.execmodel.generator.operatorspec.CustomOperatorSpec; import org.drools.model.codegen.execmodel.generator.operatorspec.NativeOperatorSpec; import org.drools.model.codegen.execmodel.generator.operatorspec.OperatorSpec; @@ -95,6 +96,7 @@ import org.drools.mvelcompiler.ConstraintCompiler; import org.drools.mvelcompiler.util.BigDecimalArgumentCoercion; import org.drools.util.MethodUtils; +import org.drools.util.Pair; import org.drools.util.TypeResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -223,18 +225,16 @@ private Optional toTypedExpressionRec(Expression drlxExpr) { TypedExpression left = optLeft.get(); TypedExpression right = optRight.get(); - ArithmeticCoercedExpression.ArithmeticCoercedExpressionResult coerced; - try { - coerced = new ArithmeticCoercedExpression(left, right, operator).coerce(); - } catch (ArithmeticCoercedExpression.ArithmeticCoercedExpressionException e) { - logger.error("Failed to coerce : {}", e.getInvalidExpressionErrorResult()); - return empty(); + final BinaryExpr combo; + final Pair numberAndStringCoercionResult = + NumberAndStringArithmeticOperationCoercion.coerceIfNeeded(operator, left, right); + if (numberAndStringCoercionResult.hasLeft()) { + left = numberAndStringCoercionResult.getLeft(); } - - left = coerced.getCoercedLeft(); - right = coerced.getCoercedRight(); - - final BinaryExpr combo = new BinaryExpr(left.getExpression(), right.getExpression(), operator); + if (numberAndStringCoercionResult.hasRight()) { + right = numberAndStringCoercionResult.getRight(); + } + combo = new BinaryExpr(left.getExpression(), right.getExpression(), operator); if (shouldConvertArithmeticBinaryToMethodCall(operator, left.getType(), right.getType())) { Expression expression = convertArithmeticBinaryToMethodCall(combo, of(typeCursor), ruleContext); @@ -380,6 +380,10 @@ private Optional toTypedExpressionRec(Expression drlxExpr) { return of(new TypedExpression(drlxExpr, type)); } + if (drlxExpr instanceof ConditionalExpr) { + return of(new TypedExpression(drlxExpr, Boolean.class)); + } + if (drlxExpr.isAssignExpr()) { AssignExpr assignExpr = drlxExpr.asAssignExpr(); diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/visitor/accumulate/AccumulateVisitor.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/visitor/accumulate/AccumulateVisitor.java index 773bcc19a3c..e998f3a3578 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/visitor/accumulate/AccumulateVisitor.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/visitor/accumulate/AccumulateVisitor.java @@ -38,6 +38,7 @@ import com.github.javaparser.ast.expr.MethodReferenceExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.expr.UnaryExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.ExpressionStmt; import com.github.javaparser.ast.type.ClassOrInterfaceType; @@ -184,8 +185,11 @@ private Optional visit(AccumulateDescr.AccumulateFunctionCallDescr f if ( function.getParams().length == 0 ) { final AccumulateFunction optAccumulateFunction = getAccumulateFunction( function, Object.class ); zeroParameterFunction( basePattern, functionDSL, bindingId, optAccumulateFunction ); - } else { + } else if (function.getParams().length == 1) { optNewBinding = parseFirstParameter( basePattern, input, function, functionDSL, bindingId ); + } else { + throw new AccumulateParsingFailedException( + "Function \"" + function.getFunction() + "\" cannot have more than 1 parameter"); } if ( bindingId != null ) { @@ -204,8 +208,8 @@ private Optional parseFirstParameter(PatternDescr basePattern, BaseD final String accumulateFunctionParameterStr = function.getParams()[0]; final Expression accumulateFunctionParameter = DrlxParseUtil.parseExpression(accumulateFunctionParameterStr).getExpr(); - if (accumulateFunctionParameter instanceof BinaryExpr) { - return binaryExprParameter(basePattern, function, functionDSL, bindingId, accumulateFunctionParameterStr); + if (accumulateFunctionParameter instanceof BinaryExpr || accumulateFunctionParameter instanceof UnaryExpr) { + return bindingParameter(basePattern, function, functionDSL, bindingId, accumulateFunctionParameterStr); } if (parameterNeedsConvertionToMethodCallExpr(accumulateFunctionParameter)) { @@ -217,7 +221,11 @@ private Optional parseFirstParameter(PatternDescr basePattern, BaseD } else if (accumulateFunctionParameter instanceof LiteralExpr) { literalExprParameter(basePattern, function, functionDSL, bindingId, accumulateFunctionParameter); } else { - throw new AccumulateParsingFailedException("Invalid expression " + accumulateFunctionParameterStr); + throw new AccumulateParsingFailedException( + "The expression \"" + accumulateFunctionParameterStr + + "\" in function \"" + function.getFunction() + + "\" of type \"" + accumulateFunctionParameter.getClass().getSimpleName() + + "\" is not managed in " + this.getClass().getSimpleName()); } return Optional.empty(); @@ -406,10 +414,10 @@ private boolean parameterNeedsConvertionToMethodCallExpr(Expression accumulateFu return accumulateFunctionParameter.isMethodCallExpr() || accumulateFunctionParameter.isArrayAccessExpr() || accumulateFunctionParameter.isFieldAccessExpr(); } - private Optional binaryExprParameter(PatternDescr basePattern, AccumulateDescr.AccumulateFunctionCallDescr function, MethodCallExpr functionDSL, String bindingId, String accumulateFunctionParameterStr) { + private Optional bindingParameter(PatternDescr basePattern, AccumulateDescr.AccumulateFunctionCallDescr function, MethodCallExpr functionDSL, String bindingId, String accumulateFunctionParameterStr) { final DrlxParseResult parseResult = ConstraintParser.defaultConstraintParser(context, packageModel).drlxParse(Object.class, bindingId, accumulateFunctionParameterStr); - Optional optNewBinding = parseResult.acceptWithReturnValue(new ParseResultVisitor>() { + return parseResult.acceptWithReturnValue(new ParseResultVisitor<>() { @Override public Optional onSuccess(DrlxParseSuccess drlxParseResult) { SingleDrlxParseSuccess singleResult = (SingleDrlxParseSuccess) drlxParseResult; @@ -436,7 +444,6 @@ public Optional onFail(DrlxParseFail failure) { return Optional.empty(); } }); - return optNewBinding; } private void zeroParameterFunction(PatternDescr basePattern, MethodCallExpr functionDSL, String bindingId, AccumulateFunction accumulateFunction) { diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AccumulateTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AccumulateTest.java index 09352efff22..f46c7f77084 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AccumulateTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AccumulateTest.java @@ -60,6 +60,8 @@ import org.drools.model.codegen.execmodel.domain.TargetPolicy; import org.drools.model.codegen.execmodel.oopathdtables.InternationalAddress; import org.junit.Test; +import org.kie.api.builder.Message; +import org.kie.api.builder.Results; import org.kie.api.definition.type.FactType; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.AccumulateFunction; @@ -4204,4 +4206,79 @@ public void testJoinInAccumulate() { assertThat(results.size()).isEqualTo(2); assertThat(results).containsExactlyInAnyOrder(new Result(0), new Result(75)); } + + @Test + public void testAccumulateSumMultipleParametersExpression() { + String str = + "import " + Person.class.getCanonicalName() + ";" + + "import " + Result.class.getCanonicalName() + ";" + + "rule X when\n" + + " accumulate ( $i : Integer() and $p: Person ( name.length >= $i ); \n" + + " $sum : sum($p.getAge(), $p.getName()) \n" + + " ) \n" + + "then\n" + + " insert(new Result($sum));\n" + + "end"; + + Results results = createKieBuilder(str).getResults(); + if (testRunType.isExecutableModel()) { + assertThat(results.getMessages(Message.Level.ERROR).get(0).getText().contains( + "Function \"sum\" cannot have more than 1 parameter")).isTrue(); + } else { + // At this time, this error is thrown with executable model only. + assertThat(results.getMessages(Message.Level.ERROR).isEmpty()).isTrue(); + } + } + + @Test + public void testAccumulateSumUnaryExpression() { + String str = + "import " + Person.class.getCanonicalName() + ";" + + "import " + Result.class.getCanonicalName() + ";" + + "rule X when\n" + + " accumulate ( $p: Person ( getName().startsWith(\"M\") ); \n" + + " $sum : sum(-$p.getAge()) \n" + + " ) \n" + + "then\n" + + " insert(new Result($sum));\n" + + "end"; + + KieSession kieSession = getKieSession( str ); + + kieSession.insert(new Person("Mark", 37)); + kieSession.insert(new Person("Edson", 35)); + kieSession.insert(new Person("Mario", 40)); + + kieSession.fireAllRules(); + + Collection results = getObjectsIntoList(kieSession, Result.class); + assertThat(results.size()).isEqualTo(1); + assertThat(results.iterator().next().getValue()).isEqualTo(-77); + } + + @Test + public void testAccumulateSumBinaryExpWithNestedUnaryExpression() { + String str = + "import " + Person.class.getCanonicalName() + ";" + + "import " + Result.class.getCanonicalName() + ";" + + "rule X when\n" + + " accumulate ( $p: Person ( getName().startsWith(\"M\") ); \n" + + " $sum : sum(-$p.getAge() + $p.getAge()) \n" + + " ) \n" + + "then\n" + + " insert(new Result($sum));\n" + + "end"; + + KieSession kieSession = getKieSession( str ); + + kieSession.insert(new Person("Mark", 37)); + kieSession.insert(new Person("Edson", 35)); + kieSession.insert(new Person("Mario", 40)); + + kieSession.fireAllRules(); + + Collection results = getObjectsIntoList(kieSession, Result.class); + assertThat(results.size()).isEqualTo(1); + assertThat(results.iterator().next().getValue()).isEqualTo(0); + } } diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/BindingTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/BindingTest.java index 7ecf142262d..9512691161f 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/BindingTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/BindingTest.java @@ -475,4 +475,69 @@ public void test3BindingOn3ConditionsWithOrAnd() { ksession.fireAllRules(); assertThat(result).isEmpty(); } + + @Test + public void testConstraintExpression() { + String str = "package constraintexpression\n" + + "\n" + + "import " + Person.class.getCanonicalName() + "\n" + + "import java.util.List; \n" + + "global List booleanListGlobal; \n" + + "rule \"r1\"\n" + + "when \n" + + " $p : Person($booleanVariable: (name != null))\n" + + "then \n" + + " System.out.println($booleanVariable); \n" + + " System.out.println($p); \n" + + " booleanListGlobal.add($booleanVariable); \n " + + "end \n"; + + KieSession ksession = getKieSession(str); + try { + final List booleanListGlobal = new ArrayList<>(); + ksession.setGlobal("booleanListGlobal", booleanListGlobal); + Person person = new Person("someName"); + ksession.insert(person); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + assertThat(booleanListGlobal).isNotEmpty().containsExactly(Boolean.TRUE); + } finally { + ksession.dispose(); + } + } + + /** + * This test checks that a rule is not fired, when a binding is + * enclosed in parentheses. This is intentional behaviour, agreed in discussions, + * which may be revised in the future. + */ + @Test + public void testIgnoreConstraintInParentheses() { + String str = "package constraintexpression\n" + + "\n" + + "import " + Person.class.getCanonicalName() + "\n" + + "import java.util.List; \n" + + "global List booleanListGlobal; \n" + + "rule \"r1\"\n" + + "when \n" + + " $p : Person($booleanVariable: (name == null))\n" + + "then \n" + + " System.out.println($booleanVariable); \n" + + " System.out.println($p); \n" + + " booleanListGlobal.add($booleanVariable); \n " + + "end \n"; + + KieSession ksession = getKieSession(str); + try { + final List booleanListGlobal = new ArrayList<>(); + ksession.setGlobal("booleanListGlobal", booleanListGlobal); + Person person = new Person("someName"); + ksession.insert(person); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + assertThat(booleanListGlobal).isNotEmpty().containsExactly(Boolean.FALSE); + } finally { + ksession.dispose(); + } + } } diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ConditionalExprTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ConditionalExprTest.java new file mode 100644 index 00000000000..89f89561c7d --- /dev/null +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ConditionalExprTest.java @@ -0,0 +1,87 @@ +/** + * 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.drools.model.codegen.execmodel; + +import java.util.ArrayList; +import java.util.List; + +import org.drools.model.codegen.execmodel.domain.Person; +import org.junit.Before; +import org.junit.Test; +import org.kie.api.runtime.KieSession; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConditionalExprTest extends BaseModelTest { + + private static final String RULE_STRING = "package constraintexpression\n" + + "\n" + + "import " + Person.class.getCanonicalName() + "\n" + + "import java.util.List; \n" + + "global List booleanListGlobal; \n" + + "rule \"r1\"\n" + + "when \n" + + " $p : Person($booleanVariable: (name != null ? true : false))\n" + + "then \n" + + " System.out.println($booleanVariable); \n" + + " System.out.println($p); \n" + + " booleanListGlobal.add($booleanVariable); \n " + + "end \n"; + + private KieSession ksession; + private List booleanListGlobal; + + public ConditionalExprTest(RUN_TYPE testRunType) { + super(testRunType); + } + + @Before + public void setup() { + ksession = getKieSession(RULE_STRING); + booleanListGlobal = new ArrayList<>(); + ksession.setGlobal("booleanListGlobal", booleanListGlobal); + } + + @Test + public void testConditionalExpressionWithNamedPerson() { + try { + Person person = new Person("someName"); + ksession.insert(person); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + assertThat(booleanListGlobal).isNotEmpty().containsExactly(Boolean.TRUE); + } finally { + ksession.dispose(); + } + } + + @Test + public void testConditionalExpressionWithUnnamedPerson() { + try { + Person person = new Person(); + ksession.insert(person); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + assertThat(booleanListGlobal).isNotEmpty().containsExactly(Boolean.FALSE); + } finally { + ksession.dispose(); + } + } + +} \ No newline at end of file diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ConstraintTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ConstraintTest.java new file mode 100644 index 00000000000..b3475e58c2d --- /dev/null +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ConstraintTest.java @@ -0,0 +1,90 @@ +/** + * 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.drools.model.codegen.execmodel; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import org.drools.model.codegen.execmodel.domain.Person; +import org.junit.Before; +import org.junit.Test; +import org.kie.api.runtime.KieSession; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConstraintTest extends BaseModelTest { + + private static final String RULE_STRING = "package constraintexpression\n" + + "\n" + + "import " + Person.class.getCanonicalName() + "\n" + + "import java.util.List; \n" + + "import java.math.BigDecimal; \n" + + "global List bigDecimalListGlobal; \n" + + "rule \"r1\"\n" + + "when \n" + + " $p : Person($amount: (money == null ? BigDecimal.valueOf(100.0) : money))\n" + + "then \n" + + " System.out.println($amount); \n" + + " System.out.println($p); \n" + + " bigDecimalListGlobal.add($amount); \n " + + "end \n"; + + private KieSession ksession; + + private List bigDecimalListGlobal; + + public ConstraintTest(RUN_TYPE testRunType) { + super(testRunType); + } + + @Before + public void setup() { + ksession = getKieSession(RULE_STRING); + bigDecimalListGlobal = new ArrayList<>(); + ksession.setGlobal("bigDecimalListGlobal", bigDecimalListGlobal); + } + + @Test + public void testConstraintWithMoney() { + try { + BigDecimal money = BigDecimal.valueOf(34.45); + Person person = new Person("", money); + ksession.insert(person); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + assertThat(bigDecimalListGlobal).isNotEmpty().containsExactly(money); + } finally { + ksession.dispose(); + } + } + + @Test + public void testConstraintWithoutMoney() { + try { + Person person = new Person(); + ksession.insert(person); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + assertThat(bigDecimalListGlobal).isNotEmpty().containsExactly(BigDecimal.valueOf(100.0)); + } finally { + ksession.dispose(); + } + } +} diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ArithmeticCoecionTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/NumberAndStringArithmeticOperationCoercionTest.java similarity index 97% rename from drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ArithmeticCoecionTest.java rename to drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/NumberAndStringArithmeticOperationCoercionTest.java index 012a2532add..b92641ad932 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/ArithmeticCoecionTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/NumberAndStringArithmeticOperationCoercionTest.java @@ -26,9 +26,9 @@ import static org.assertj.core.api.Assertions.assertThat; -public class ArithmeticCoecionTest extends BaseModelTest { +public class NumberAndStringArithmeticOperationCoercionTest extends BaseModelTest { - public ArithmeticCoecionTest(RUN_TYPE testRunType) { + public NumberAndStringArithmeticOperationCoercionTest(RUN_TYPE testRunType) { super(testRunType); } diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/TypeCoercionTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/TypeCoercionTest.java index 95a49e3ac1b..67cd73f3bd3 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/TypeCoercionTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/TypeCoercionTest.java @@ -612,4 +612,30 @@ public void testFloatOperation() { assertThat(list.size()).isEqualTo(1); assertThat(list.get(0)).isEqualTo("Mario"); } + + @Test + public void testCoerceObjectToString() { + String str = "package constraintexpression\n" + + "\n" + + "import " + Person.class.getCanonicalName() + "\n" + + "import java.util.List; \n" + + "rule \"r1\"\n" + + "when \n" + + " $p: Person() \n" + + " String(this == \"someString\" + $p)\n" + + "then \n" + + " System.out.println($p); \n" + + "end \n"; + + KieSession ksession = getKieSession(str); + try { + Person person = new Person("someName"); + ksession.insert(person); + ksession.insert(new String("someStringsomeName")); + int rulesFired = ksession.fireAllRules(); + assertThat(rulesFired).isEqualTo(1); + } finally { + ksession.dispose(); + } + } } \ No newline at end of file diff --git a/drools-model/drools-mvel-parser/src/test/java/org/drools/mvel/parser/DroolsMvelParserTest.java b/drools-model/drools-mvel-parser/src/test/java/org/drools/mvel/parser/DroolsMvelParserTest.java index ea5f6987194..49910e35269 100644 --- a/drools-model/drools-mvel-parser/src/test/java/org/drools/mvel/parser/DroolsMvelParserTest.java +++ b/drools-model/drools-mvel-parser/src/test/java/org/drools/mvel/parser/DroolsMvelParserTest.java @@ -182,6 +182,22 @@ public void testDotFreeEnclosed() { assertThat(printNode(expression)).isEqualTo(expr); } + @Test + public void testConstantUnaryExpression() { + String expr = "-49"; + Expression expression = parseExpression( parser, expr ).getExpr(); + assertThat(printNode(expression)).isEqualTo(expr); + assertThat(expression.isUnaryExpr()).isTrue(); + } + + @Test + public void testVariableUnaryExpression() { + String expr = "-$a"; + Expression expression = parseExpression( parser, expr ).getExpr(); + assertThat(printNode(expression)).isEqualTo(expr); + assertThat(expression.isUnaryExpr()).isTrue(); + } + @Test public void testDotFreeEnclosedWithNameExpr() { String expr = "(something after $a)"; @@ -402,10 +418,12 @@ public void testAndWithImplicitNegativeParameter() { BinaryExpr first = (BinaryExpr) comboExpr.getLeft(); assertThat(toString(first.getLeft())).isEqualTo("value"); assertThat(toString(first.getRight())).isEqualTo("-2"); + assertThat(first.getRight().isUnaryExpr()).isTrue(); assertThat(first.getOperator()).isEqualTo(Operator.GREATER); HalfBinaryExpr second = (HalfBinaryExpr) comboExpr.getRight(); assertThat(toString(second.getRight())).isEqualTo("-1"); + assertThat(second.getRight().isUnaryExpr()).isTrue(); assertThat(second.getOperator()).isEqualTo(HalfBinaryExpr.Operator.LESS); } diff --git a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java index 7d03ed6cb31..38809896b71 100644 --- a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java +++ b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java @@ -480,7 +480,7 @@ public boolean canConvertFrom(Class cls) { public static class StringCoercionCompatibilityEvaluator extends CompatibilityStrategy.DefaultCompatibilityEvaluator { - private static final CompatibilityStrategy.CompatibilityEvaluator INSTANCE = new StringCoercionCompatibilityEvaluator(); + public static final CompatibilityStrategy.CompatibilityEvaluator INSTANCE = new StringCoercionCompatibilityEvaluator(); private StringCoercionCompatibilityEvaluator() { } diff --git a/drools-mvel/src/test/java/org/drools/mvel/MVELDateCoercionTest.java b/drools-mvel/src/test/java/org/drools/mvel/MVELDateCoercionTest.java index 4eec3e14162..9e06729024a 100644 --- a/drools-mvel/src/test/java/org/drools/mvel/MVELDateCoercionTest.java +++ b/drools-mvel/src/test/java/org/drools/mvel/MVELDateCoercionTest.java @@ -20,13 +20,23 @@ import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import org.drools.mvel.MVELConstraintBuilder.StringCoercionCompatibilityEvaluator; import org.drools.util.DateUtils; import org.drools.mvel.expr.MVELDateCoercion; import org.junit.Test; +import org.mvel2.DataConversion; +import org.mvel2.MVEL; +import org.mvel2.ParserContext; +import org.mvel2.util.CompatibilityStrategy; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; +import static org.mvel2.MVEL.executeExpression; public class MVELDateCoercionTest { @@ -51,4 +61,66 @@ public void testString() throws Exception { assertThat(co.convertFrom(dt)).isEqualTo(dt_); } + @Test + public void testCoercionDuringAnalyze() { + CompatibilityStrategy.setCompatibilityEvaluator(StringCoercionCompatibilityEvaluator.INSTANCE); + + DataConversion.addConversionHandler(Date.class, + new MVELDateCoercion()); + + String expr = "f.departureTime >= \"01-Jan-1970\" && f.departureTime <= \"01-Jan-2018\""; + + ParserContext ctx = new ParserContext(); + ctx.setStrongTyping(true); + ctx.setStrictTypeEnforcement(true); + ctx.addInput("f", Flight.class); + + Class cls = MVEL.analyze( expr, + ctx ); + + assertThat(cls).isNotNull(); + } + + public static class Flight { + private Date departureTime; + + public Flight(Date departureTime) { + this.departureTime = departureTime; + } + + public Date getDepartureTime() { + return departureTime; + } + + public void setDepartureTime(Date departureTime) { + this.departureTime = departureTime; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Flight flight = (Flight) o; + + return Objects.equals(departureTime, flight.departureTime); + } + + @Override + public int hashCode() { + return departureTime != null ? departureTime.hashCode() : 0; + } + + @Override + public String toString() { + return "Flight{" + + "departureTime=" + departureTime + + '}'; + } + } } diff --git a/drools-persistence/drools-persistence-jpa/pom.xml b/drools-persistence/drools-persistence-jpa/pom.xml index 2192238f8c4..be32646c131 100644 --- a/drools-persistence/drools-persistence-jpa/pom.xml +++ b/drools-persistence/drools-persistence-jpa/pom.xml @@ -39,7 +39,6 @@ - org.hibernate.dialect.H2Dialect org.h2.jdbcx.JdbcDataSource org.h2.Driver @@ -74,6 +73,7 @@ ${maven.jdbc.driver.jar} + -javaagent:${settings.localRepository}/org/apache/openjpa/openjpa/${version.org.apache.openjpa}/openjpa-${version.org.apache.openjpa}.jar @@ -161,22 +161,12 @@ test - - - org.hibernate.orm - hibernate-core + + org.apache.openjpa + openjpa test - - - org.glassfish.jaxb - jaxb-runtime - - - org.jboss - jandex - - + io.smallrye jandex @@ -193,12 +183,18 @@ logback-classic test + + org.jboss.logging + jboss-logging + test + com.h2database h2 test + org.jboss.narayana.jta narayana-jta diff --git a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/SessionInfo.java b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/SessionInfo.java index 54fd6cc38ad..2c16a42b663 100644 --- a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/SessionInfo.java +++ b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/SessionInfo.java @@ -38,7 +38,7 @@ public class SessionInfo implements PersistentSession { private @Id - @GeneratedValue(strategy = GenerationType.AUTO, generator="sessionInfoIdSeq") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="sessionInfoIdSeq") Long id; @Version diff --git a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/WorkItemInfo.java b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/WorkItemInfo.java index ce26c9d2f87..2f65a971ca8 100644 --- a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/WorkItemInfo.java +++ b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/WorkItemInfo.java @@ -55,7 +55,7 @@ public class WorkItemInfo implements PersistentWorkItem { private static final Logger logger = LoggerFactory.getLogger(WorkItemInfo.class); @Id - @GeneratedValue(strategy = GenerationType.AUTO, generator="workItemInfoIdSeq") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="workItemInfoIdSeq") private Long workItemId; @Version diff --git a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/OptimisticLockRetryInterceptor.java b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/OptimisticLockRetryInterceptor.java index 91485667576..046085e94d5 100644 --- a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/OptimisticLockRetryInterceptor.java +++ b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/OptimisticLockRetryInterceptor.java @@ -18,15 +18,15 @@ */ package org.drools.persistence.jpa; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.persistence.OptimisticLockException; import org.drools.commands.impl.AbstractInterceptor; import org.kie.api.runtime.Executable; import org.kie.api.runtime.RequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jakarta.persistence.OptimisticLockException; -import java.util.concurrent.atomic.AtomicInteger; - /** * ExecutableInterceptor that is capable of retrying command execution. It is intended to retry only if right exception * has been thrown. By default it will look for org.hibernate.StaleObjectStateException and only @@ -58,14 +58,14 @@ public class OptimisticLockRetryInterceptor extends AbstractInterceptor { private static final ThreadLocal invocationsCounter = new ThreadLocal<>(); public OptimisticLockRetryInterceptor() { - String clazz = System.getProperty("org.kie.optlock.exclass", "org.hibernate.StaleObjectStateException"); + String clazz = System.getProperty("org.kie.optlock.exclass", "org.apache.openjpa.persistence.OptimisticLockException"); try { targetExceptionClass = Class.forName(clazz); } catch (ClassNotFoundException e) { logger.error("Optimistic locking exception class not found {}", clazz, e); } - clazz = System.getProperty("org.kie.constraint.exclass", "org.hibernate.exception.ConstraintViolationException"); + clazz = System.getProperty("org.kie.constraint.exclass", "org.apache.openjpa.util.InvalidStateException"); try { targetConstraintViolationExceptionClass = Class.forName(clazz); } catch (ClassNotFoundException e) { diff --git a/drools-persistence/drools-persistence-jpa/src/test/filtered-resources/META-INF/persistence.xml b/drools-persistence/drools-persistence-jpa/src/test/filtered-resources/META-INF/persistence.xml index 845fcc9f72a..c57bf903ac7 100644 --- a/drools-persistence/drools-persistence-jpa/src/test/filtered-resources/META-INF/persistence.xml +++ b/drools-persistence/drools-persistence-jpa/src/test/filtered-resources/META-INF/persistence.xml @@ -25,7 +25,7 @@ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"> - org.hibernate.jpa.HibernatePersistenceProvider + org.apache.openjpa.persistence.PersistenceProviderImpl jdbc/testDS1 org.drools.persistence.info.SessionInfo org.drools.persistence.info.WorkItemInfo @@ -34,19 +34,7 @@ org.drools.persistence.jta.TransactionTestObject - - - - - - - - - - - - - + diff --git a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/JtaTransactionManagerTest.java b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/JtaTransactionManagerTest.java index 449fc7759ae..f8cbff8e23a 100644 --- a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/JtaTransactionManagerTest.java +++ b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/JtaTransactionManagerTest.java @@ -35,7 +35,6 @@ import org.drools.persistence.api.TransactionManager; import org.drools.persistence.jpa.JpaPersistenceContextManager; import org.drools.persistence.util.DroolsPersistenceUtil; -import org.hibernate.TransientObjectException; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -150,7 +149,7 @@ public void showingTransactionTestObjectsNeedTransactions() throws Exception { tx.commit(); } catch( Exception e ) { - if( e instanceof RollbackException || e.getCause() instanceof TransientObjectException ) { + if ( e instanceof RollbackException ) { rollBackExceptionthrown = true; if( tx.getStatus() == 1 ) { diff --git a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/TransactionTestObject.java b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/TransactionTestObject.java index c9930291c87..e2d1b407c88 100644 --- a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/TransactionTestObject.java +++ b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/jta/TransactionTestObject.java @@ -41,7 +41,7 @@ public class TransactionTestObject implements Serializable { private static final long serialVersionUID = 8991032325499307158L; @Id - @GeneratedValue(strategy=GenerationType.AUTO, generator="txTestIdSeq") + @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="txTestIdSeq") @Column(name="ID") private Long id; diff --git a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java index 730575a1a4d..e2e75149da6 100644 --- a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java +++ b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java @@ -18,6 +18,11 @@ */ package org.drools.persistence.map.impl; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import jakarta.persistence.EntityManagerFactory; import org.drools.persistence.jpa.marshaller.JPAPlaceholderResolverStrategy; import org.drools.persistence.jta.JtaTransactionManager; import org.drools.persistence.util.DroolsPersistenceUtil; @@ -34,11 +39,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jakarta.persistence.EntityManagerFactory; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; - import static org.drools.persistence.util.DroolsPersistenceUtil.DROOLS_PERSISTENCE_UNIT_NAME; import static org.drools.persistence.util.DroolsPersistenceUtil.OPTIMISTIC_LOCKING; import static org.drools.persistence.util.DroolsPersistenceUtil.PESSIMISTIC_LOCKING; @@ -115,7 +115,7 @@ protected long getSavedSessionsCount() { if( useTransactions ) { transactionOwner = txm.begin(); } - long savedSessionsCount = emf.createEntityManager().createQuery( "FROM SessionInfo" ).getResultList().size(); + long savedSessionsCount = emf.createEntityManager().createQuery( "SELECT DISTINCT id FROM SessionInfo AS id" ).getResultList().size(); if( useTransactions ) { txm.commit(transactionOwner); } diff --git a/drools-quarkus-extension/drools-quarkus-quickstart-test/guide.adoc b/drools-quarkus-extension/drools-quarkus-quickstart-test/guide.adoc index 3ce852d4a17..bb74e51dc27 100644 --- a/drools-quarkus-extension/drools-quarkus-quickstart-test/guide.adoc +++ b/drools-quarkus-extension/drools-quarkus-quickstart-test/guide.adoc @@ -17,8 +17,8 @@ To complete this guide, you need: * less than 15 minutes * an IDE -* JDK 11+ installed with `JAVA_HOME` configured appropriately -* Apache Maven 3.8.6+ +* JDK 17+ installed with `JAVA_HOME` configured appropriately +* Apache Maven 3.9.3+ * Docker * link:{quarkus-guides-url}/building-native-image[GraalVM installed if you want to run in native mode] diff --git a/drools-quarkus-extension/drools-quarkus/pom.xml b/drools-quarkus-extension/drools-quarkus/pom.xml index 75ef13e08ac..b30722604f1 100644 --- a/drools-quarkus-extension/drools-quarkus/pom.xml +++ b/drools-quarkus-extension/drools-quarkus/pom.xml @@ -29,8 +29,10 @@ 999-SNAPSHOT - Drools :: Quarkus Extension :: Runtime drools-quarkus + Drools Quarkus - Runtime + Define and execute your business rules with Drools + https://www.drools.org/ org.drools.quarkus.runtime @@ -124,7 +126,7 @@ ${project.groupId}:${project.artifactId}-deployment:${project.version} - org.drools.drl + org.drools diff --git a/drools-quarkus-extension/drools-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/drools-quarkus-extension/drools-quarkus/src/main/resources/META-INF/quarkus-extension.yaml index 714ddd8efc2..0ed9233b1d2 100644 --- a/drools-quarkus-extension/drools-quarkus/src/main/resources/META-INF/quarkus-extension.yaml +++ b/drools-quarkus-extension/drools-quarkus/src/main/resources/META-INF/quarkus-extension.yaml @@ -18,9 +18,15 @@ # --- -name: "Drools-DRL" +name: "Drools" metadata: keywords: - "drools" - "rules" + - "rule engine" + - "artificial intelligence" - "DRL" + guide: "https://quarkus.io/guides/drools" + categories: + - "business-automation" + status: "stable" \ No newline at end of file diff --git a/drools-traits/pom.xml b/drools-traits/pom.xml index 09f4e5a7875..896e33b2774 100644 --- a/drools-traits/pom.xml +++ b/drools-traits/pom.xml @@ -41,7 +41,6 @@ - org.hibernate.dialect.H2Dialect org.h2.jdbcx.JdbcDataSource org.h2.Driver @@ -128,6 +127,11 @@ org.slf4j slf4j-api + + org.jboss.logging + jboss-logging + test + ch.qos.logback logback-classic @@ -156,23 +160,12 @@ jboss-transaction-spi-jakarta test - - - - org.hibernate.orm - hibernate-core + + org.apache.openjpa + openjpa test - - - org.glassfish.jaxb - jaxb-runtime - - - org.jboss - jandex - - + io.smallrye jandex @@ -200,10 +193,9 @@ mockito-core test - - + @@ -215,6 +207,16 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + + -javaagent:${settings.localRepository}/org/apache/openjpa/openjpa/${version.org.apache.openjpa}/openjpa-${version.org.apache.openjpa}.jar + + + + src/test/resources diff --git a/drools-traits/src/test/filtered-resources/META-INF/persistence.xml b/drools-traits/src/test/filtered-resources/META-INF/persistence.xml index 845fcc9f72a..04c82efe309 100644 --- a/drools-traits/src/test/filtered-resources/META-INF/persistence.xml +++ b/drools-traits/src/test/filtered-resources/META-INF/persistence.xml @@ -25,28 +25,16 @@ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"> - org.hibernate.jpa.HibernatePersistenceProvider - jdbc/testDS1 + org.apache.openjpa.persistence.PersistenceProviderImpl + jdbc/testDS1 org.drools.persistence.info.SessionInfo org.drools.persistence.info.WorkItemInfo - + org.drools.persistence.jta.TransactionTestObject - + - - - - - - - - - - - - - + diff --git a/drools-util/pom.xml b/drools-util/pom.xml index 7bc2377c5fc..3fd81109ddf 100644 --- a/drools-util/pom.xml +++ b/drools-util/pom.xml @@ -41,6 +41,10 @@ + + org.slf4j + slf4j-api + org.eclipse.microprofile.config microprofile-config-api diff --git a/drools-util/src/main/java/org/drools/util/FileUtils.java b/drools-util/src/main/java/org/drools/util/FileUtils.java index d62a9ef730b..799d7464980 100644 --- a/drools-util/src/main/java/org/drools/util/FileUtils.java +++ b/drools-util/src/main/java/org/drools/util/FileUtils.java @@ -42,6 +42,7 @@ private FileUtils() { /** * Retrieve the File of the given file + * This method does not guarantee the returned file if multiple files, with same name, are present in different directories * @param fileName * @return */ @@ -58,6 +59,31 @@ public static File getFile(String fileName) { return toReturn; } + /** + * Retrieve the File of the given file + * @param fileName + * @param parentDir + * @return + */ + public static File getFile(String fileName, String parentDir) { + String extension = fileName.substring(fileName.lastIndexOf('.') + 1); + File parentDirectory = new File(parentDir); + if (!parentDirectory.exists() || !parentDirectory.canRead() || !parentDirectory.isDirectory()) { + throw new IllegalArgumentException("Failed to find parent directory " + parentDir); + } + File toReturn = ResourceHelper.getFileResourcesByExtension(extension) + .stream() + .filter(file -> file.getName().equals(fileName) && + file.getParentFile() != null && + file.getParentFile().getAbsolutePath().equals(parentDirectory.getAbsolutePath())) + .findFirst() + .orElse(null); + if (toReturn == null) { + throw new IllegalArgumentException("Failed to find file " + fileName); + } + return toReturn; + } + /** * Retrieve the FileInputStream of the given file * @param fileName @@ -69,6 +95,18 @@ public static FileInputStream getFileInputStream(String fileName) throws IOExcep return new FileInputStream(sourceFile); } + /** + * Retrieve the FileInputStream of the given file + * @param fileName + * @param parentDir + * @return + * @throws IOException + */ + public static FileInputStream getFileInputStream(String fileName, String parentDir) throws IOException { + File sourceFile = getFile(fileName, parentDir); + return new FileInputStream(sourceFile); + } + /** * Retrieve the content of the given file * @param fileName @@ -84,6 +122,22 @@ public static String getFileContent(String fileName) throws IOException { return toReturn; } + /** + * Retrieve the content of the given file + * @param fileName + * @param parentDir + * @return + * @throws IOException + */ + public static String getFileContent(String fileName, String parentDir) throws IOException { + File file = getFile(fileName, parentDir); + Path path = file.toPath(); + Stream lines = Files.lines(path); + String toReturn = lines.collect(Collectors.joining("\n")); + lines.close(); + return toReturn; + } + /** * @param fileName * @param classLoader diff --git a/drools-util/src/main/java/org/drools/util/Pair.java b/drools-util/src/main/java/org/drools/util/Pair.java new file mode 100644 index 00000000000..4408263db50 --- /dev/null +++ b/drools-util/src/main/java/org/drools/util/Pair.java @@ -0,0 +1,28 @@ +package org.drools.util; + +public class Pair { + + private final K left; + private final V right; + + public Pair(K k, V v) { + this.left = k; + this.right = v; + } + + public K getLeft() { + return left; + } + + public V getRight() { + return right; + } + + public boolean hasLeft() { + return left != null; + } + + public boolean hasRight() { + return right != null; + } +} diff --git a/drools-util/src/main/java/org/drools/util/RemoveCommentsMain.java b/drools-util/src/main/java/org/drools/util/RemoveCommentsMain.java index 014a1b23bf1..024320a8b28 100644 --- a/drools-util/src/main/java/org/drools/util/RemoveCommentsMain.java +++ b/drools-util/src/main/java/org/drools/util/RemoveCommentsMain.java @@ -1,27 +1,50 @@ package org.drools.util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.stream.Collectors; public class RemoveCommentsMain { + private static final Logger LOGGER = LoggerFactory.getLogger(RemoveCommentsMain.class); + public static void main(String... args) { - for (String fileName : args) { - try { - Files.write(Path.of(fileName), removeComments(fileName).getBytes()); - } catch (IOException e) { - throw new RuntimeException(e); + final String ignoreMissingFilesArgument = args[0]; + final boolean ignoreMissingFiles = Boolean.parseBoolean(ignoreMissingFilesArgument); + for (int i = 0; i < args.length; i++) { + // If the ignoreMissingFiles argument is defined, skip it in this iteration. + if ((ignoreMissingFiles || "false".equalsIgnoreCase(ignoreMissingFilesArgument)) && i == 0) { + continue; + } else { + try { + final String fileName = args[i]; + final String result = removeComments(fileName, ignoreMissingFiles); + if (result != null) { + Files.write(Path.of(fileName), result.getBytes()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } } } } - static String removeComments(String fileName) { + static String removeComments(String fileName, final boolean ignoreMissingFiles) { try (var lines = Files.lines(Path.of(fileName))) { return lines.filter(line -> !line.startsWith("#")).collect(Collectors.joining("\n")); } catch (IOException e) { - throw new RuntimeException(e); + // Ignore non-existant files. + if (ignoreMissingFiles && e instanceof NoSuchFileException) { + LOGGER.info("Ignoring file that doesn't exist: " + fileName); + return null; + } else { + throw new RuntimeException(e); + } } } diff --git a/drools-util/src/test/java/org/drools/util/FileUtilsTest.java b/drools-util/src/test/java/org/drools/util/FileUtilsTest.java index c48d53f9e92..1a7e1064ac5 100644 --- a/drools-util/src/test/java/org/drools/util/FileUtilsTest.java +++ b/drools-util/src/test/java/org/drools/util/FileUtilsTest.java @@ -22,6 +22,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; @@ -33,14 +34,24 @@ public class FileUtilsTest { - private static final String TEST_FILE = "TestFile.txt"; + public static final String TEST_FILE = "TestFile.txt"; private static final String NOT_EXISTING_FILE = "NotExisting.txt"; + private static final String EXISTING_DIRECTORY = "subdir"; + + private static final String NOT_EXISTING_DIRECTORY = String.format(".%snotexisting", File.separator); + @Test public void getFileExisting() { final File retrieved = FileUtils.getFile(TEST_FILE); - assertThat(retrieved).exists(); - assertThat(retrieved.getName()).isEqualTo(TEST_FILE); + assertThat(retrieved).exists().hasName(TEST_FILE); + } + + @Test + public void getFileExistingFromDirectory() { + final File retrieved = FileUtils.getFile(TEST_FILE, getSubdir()); + assertThat(retrieved).exists().hasName(TEST_FILE); + assertThat(retrieved.getParentFile()).exists().isDirectory().hasName(EXISTING_DIRECTORY); } @Test @@ -48,6 +59,11 @@ public void getFileNotExisting() { assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> FileUtils.getFile(NOT_EXISTING_FILE)); } + @Test + public void getFileNotExistingDirectory() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> FileUtils.getFile(TEST_FILE, NOT_EXISTING_DIRECTORY)); + } + @Test public void getFileInputStreamExisting() throws IOException { final FileInputStream retrieved = FileUtils.getFileInputStream(TEST_FILE); @@ -80,4 +96,13 @@ public void deleteDirectory() throws IOException { assertThat(Files.exists(tempDirectory)).isFalse(); assertThat(Files.exists(tempFile)).isFalse(); } + + private static String getSubdir() { + URL subdirResource = FileUtilsTest.class.getClassLoader().getResource(EXISTING_DIRECTORY); + if (subdirResource == null) { + throw new RuntimeException("Failed to find subdir folder"); + } else { + return subdirResource.getFile(); + } + } } \ No newline at end of file diff --git a/drools-util/src/test/java/org/drools/util/RemoveCommentsTest.java b/drools-util/src/test/java/org/drools/util/RemoveCommentsTest.java index 91bbceba3dd..1b529b5acc7 100644 --- a/drools-util/src/test/java/org/drools/util/RemoveCommentsTest.java +++ b/drools-util/src/test/java/org/drools/util/RemoveCommentsTest.java @@ -8,7 +8,7 @@ public class RemoveCommentsTest { @Test public void test() { - String result = RemoveCommentsMain.removeComments("src/test/resources/commented.properties"); + String result = RemoveCommentsMain.removeComments("src/test/resources/commented.properties", false); String expected = "provides-capabilities=org.drools.drl\ndeployment-artifact=org.drools\\:drools-quarkus-deployment\\:999-SNAPSHOT"; assertEquals(expected, result); } diff --git a/drools-util/src/test/java/org/drools/util/ResourceHelperTest.java b/drools-util/src/test/java/org/drools/util/ResourceHelperTest.java index e33653772c4..f314dfc7017 100644 --- a/drools-util/src/test/java/org/drools/util/ResourceHelperTest.java +++ b/drools-util/src/test/java/org/drools/util/ResourceHelperTest.java @@ -29,6 +29,7 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.drools.util.FileUtilsTest.TEST_FILE; import static org.drools.util.ResourceHelper.getFileResourcesByExtension; import static org.drools.util.ResourceHelper.getFileResourcesFromDirectory; import static org.drools.util.ResourceHelper.getResourcesByExtension; @@ -37,14 +38,13 @@ public class ResourceHelperTest { - private static final String TEST_FILE = "TestFile.txt"; @Test public void getResourcesByExtensionTest() { Collection resources = getResourcesByExtension("txt"); assertThat(resources) - .hasSize(1) - .anyMatch(elem -> elem.endsWith(TEST_FILE)); + .hasSize(2) + .allMatch(elem -> elem.endsWith(TEST_FILE)); } @Test @@ -64,11 +64,11 @@ public void getResourcesFromDirectoryTest() { List classPathElements = Arrays.asList(ResourceHelper.getClassPathElements()); Optional testFolder = classPathElements.stream().filter(elem -> elem.contains("test-classes")).findFirst(); - assertThat(testFolder.isPresent()).isTrue(); + assertThat(testFolder).isPresent(); File dir = new File(testFolder.get()); String regex = ".*" + TEST_FILE; Collection filesFound = getResourcesFromDirectory(dir, Pattern.compile(regex)); - assertThat(filesFound).hasSize(1); + assertThat(filesFound).hasSize(2); assertThat(getResourcesFromDirectory(null, null)).isEmpty(); assertThat(getResourcesFromDirectory(dir, Pattern.compile("noMatch"))).isEmpty(); @@ -108,9 +108,9 @@ public void getClassPathElementsTest() { public void internalGetResourcesTest() { List classPathElements = Arrays.asList(ResourceHelper.getClassPathElements()); Optional testFolder = classPathElements.stream().filter(elem -> elem.contains("test-classes")).findFirst(); - assertThat(testFolder.isPresent()).isTrue(); + assertThat(testFolder).isPresent(); Collection filesFound = internalGetResources(testFolder.get(), Pattern.compile(".*\\.txt$")); - assertThat(filesFound.size()).isEqualTo(1); + assertThat(filesFound).hasSize(2); assertThat(internalGetResources(filesFound.iterator().next(), Pattern.compile(".*\\.txt$"))).isEmpty(); } @@ -133,7 +133,7 @@ public void internalGetResourcesNotExisting() { private void commonVerifyCollectionWithExpectedFile(final Collection toVerify, String expectedFile) { assertThat(toVerify).isNotNull(); - assertThat(toVerify).hasSize(1) + assertThat(toVerify).hasSize(2) .allMatch(file -> file.exists() && file.getName().equals(expectedFile)); } diff --git a/drools-util/src/test/resources/subdir/TestFile.txt b/drools-util/src/test/resources/subdir/TestFile.txt new file mode 100644 index 00000000000..9e95ffa46c8 --- /dev/null +++ b/drools-util/src/test/resources/subdir/TestFile.txt @@ -0,0 +1 @@ +// Empty file \ No newline at end of file diff --git a/jpmml-migration-recipe/Readme.md b/jpmml-migration-recipe/Readme.md deleted file mode 100644 index dbf554dcea0..00000000000 --- a/jpmml-migration-recipe/Readme.md +++ /dev/null @@ -1,53 +0,0 @@ -JPPML migration recipe -====================== - -The jpmml-recipe contains code needed to migrate jpmml from version 1.5.1 to 1.6.4. - -The main `JPMMLRecipe` features the OpenRewrite recipe declarative chain-ability to re-use some already existing recipes, so that - -1. It invokes `ChangeType` for classes that changed name/package, but kept the same method signature -2. It invokes `JPMMLCodeRecipe` for more fine-grained manipulation, e.g. removal of `FieldName` usage and replacement of `ScoreDistribution`; this is actually done inside `JPMMLVisitor` -3. It invokes `RemoveUnusedImports` to remove unused imports - -There are three main modification steps: - -1. `JPMMLVisitor` -2. `JPMMLCodeRecipe` -3. `JPMMLRecipe` - -for each of which there is a specific unit test class. -Testing of `JPMMLVisitor` is focused on very low level LST modification. -Testing of `JPMMLCodeRecipe` is focused on the overall modifications implemented in this module. -Testing of `JPMMLRecipe` is focused on the full modifications applied by all the involved recipes. It is at this phase that the final, expected result should be evaluated. - -The `CommonTestingUtilities` has been thought to be re-usable by different recipes, even if currently defined in that module - -Usage -===== - -To execute `JPMMLRecipe`, simply add the following snippet in the target project's pom - -```xml - - org.openrewrite.maven - rewrite-maven-plugin - - - org.kie.openrewrite.recipe.jpmml.JPMMLRecipe - - - - - org.kie - jpmml-migration-recipe - - - -``` - -and issue - -`mvn rewrite:run` - - - diff --git a/jpmml-migration-recipe/pom.xml b/jpmml-migration-recipe/pom.xml deleted file mode 100644 index 7efc9d5232b..00000000000 --- a/jpmml-migration-recipe/pom.xml +++ /dev/null @@ -1,200 +0,0 @@ - - - - - 4.0.0 - - org.kie - drools-build-parent - 999-SNAPSHOT - ../build-parent/pom.xml - - - jpmml-migration-recipe - - Kie :: Jpmml Migration Recipe - OpenRewrite recipe to migrate JPMML model library from 1.5.1 to 1.6.4 - - - org.kie.jpmml.migration.recipe - - - - - - org.openrewrite.recipe - rewrite-recipe-bom - 1.19.4 - pom - import - - - org.jpmml - pmml-model - 1.5.1 - test - - - - - - - - - org.openrewrite - rewrite-java - compile - - - org.jboss - jandex - - - - - - - org.openrewrite - rewrite-java-11 - runtime - - - - - org.openrewrite - rewrite-maven - compile - - - - - org.openrewrite - rewrite-yaml - compile - - - - - org.openrewrite - rewrite-properties - compile - - - - - org.openrewrite - rewrite-xml - compile - - - - - - org.slf4j - slf4j-api - - - - - org.openrewrite - rewrite-test - test - - - commons-logging - commons-logging - - - - - - org.jpmml - pmml-model - test - - - - ch.qos.logback - logback-classic - test - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - - org.assertj - assertj-core - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.junit.jupiter - junit-jupiter-engine - ${version.org.junit} - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - download_new_pmml - - get - - validate - - org.jpmml - pmml-model - 1.6.4 - false - - - - - - - - diff --git a/jpmml-migration-recipe/src/main/java/org/kie/openrewrite/recipe/jpmml/JPMMLCodeRecipe.java b/jpmml-migration-recipe/src/main/java/org/kie/openrewrite/recipe/jpmml/JPMMLCodeRecipe.java deleted file mode 100644 index e2071a3171e..00000000000 --- a/jpmml-migration-recipe/src/main/java/org/kie/openrewrite/recipe/jpmml/JPMMLCodeRecipe.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 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.openrewrite.recipe.jpmml; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.jetbrains.annotations.NotNull; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; -import org.openrewrite.Recipe; -import org.openrewrite.java.JavaVisitor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class JPMMLCodeRecipe extends Recipe { - - private static final Logger logger = LoggerFactory.getLogger(JPMMLCodeRecipe.class); - - @Option(displayName = "Old fully-qualified type name", - description = "Fully-qualified class name of the original instantiated type.", - example = "org.dmg.pmml.ScoreDistribution") - @NotNull - String oldInstantiatedFullyQualifiedTypeName; - - @Option(displayName = "New fully-qualified type name", - description = "Fully-qualified class name of the replacement type. The `OuterClassName$NestedClassName` naming convention should be used for nested classes.", - example = "org.dmg.pmml.ComplexScoreDistributions") - @NotNull - String newInstantiatedFullyQualifiedTypeName; - - @JsonCreator - public JPMMLCodeRecipe(@NotNull @JsonProperty("oldInstantiatedFullyQualifiedTypeName") String oldInstantiatedFullyQualifiedTypeName, - @NotNull @JsonProperty("newInstantiatedFullyQualifiedTypeName") String newInstantiatedFullyQualifiedTypeName) { - this.oldInstantiatedFullyQualifiedTypeName = oldInstantiatedFullyQualifiedTypeName; - this.newInstantiatedFullyQualifiedTypeName = newInstantiatedFullyQualifiedTypeName; - logger.info("Created new instance... "); - } - - - - @Override - public String getDisplayName() { - return "JPMML Update Code recipe"; - } - - @Override - public String getDescription() { - return "Migrate JPMML Code version from 1.5.1 to 1.6.4."; - } - - - - @Override - protected JavaVisitor getVisitor() { - logger.info("Retrieving new visitor..."); - return new JPMMLVisitor(oldInstantiatedFullyQualifiedTypeName, newInstantiatedFullyQualifiedTypeName); - } - - - -} \ No newline at end of file diff --git a/jpmml-migration-recipe/src/main/java/org/kie/openrewrite/recipe/jpmml/JPMMLVisitor.java b/jpmml-migration-recipe/src/main/java/org/kie/openrewrite/recipe/jpmml/JPMMLVisitor.java deleted file mode 100644 index ba8db77801a..00000000000 --- a/jpmml-migration-recipe/src/main/java/org/kie/openrewrite/recipe/jpmml/JPMMLVisitor.java +++ /dev/null @@ -1,506 +0,0 @@ -/** - * 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.openrewrite.recipe.jpmml; - -import org.openrewrite.ExecutionContext; -import org.openrewrite.Tree; -import org.openrewrite.java.ChangeType; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.*; -import org.openrewrite.marker.Markers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.nio.file.Path; -import java.util.*; - -public class JPMMLVisitor extends JavaVisitor { - - public static final String NEW_JPMML_MODEL = "pmml-model-1.6.4.jar"; - - public static final String NEW_JPMML_MAVEN_PATH = String.format("%1$s%2$s.m2%2$srepository%2$sorg%2$sjpmml%2$spmml-model%2$s1.6.4%2$s%3$s", System.getProperty("user.home"), File.separator, NEW_JPMML_MODEL); - - private static final Logger logger = LoggerFactory.getLogger(JPMMLVisitor.class); - static final String JPMML_MODEL_PACKAGE_BASE = "org.jpmml.model"; - static final String DMG_PMML_MODEL_PACKAGE_BASE = "org.dmg.pmml"; - final JavaType.Class originalInstantiatedType; - final JavaType targetInstantiatedType; - - private static final String FIELD_NAME_FQDN = "org.dmg.pmml.FieldName"; - private static final String MODEL_NAME_FQDN = "org.dmg.pmml.Model"; - private static final String MINING_FUNCTION_NAME_FQDN = "org.dmg.pmml.MiningFunction"; - private static final String MINING_SCHEMA_NAME_FQDN = "org.dmg.pmml.MiningSchema"; - - private static final String NUMERIC_PREDICTOR_FQDN = "org.dmg.pmml.regression.NumericPredictor"; - - private static final String CATEGORICAL_PREDICTOR_FQDN = "org.dmg.pmml.regression.CategoricalPredictor"; - - private static final List GET_NAME_TO_GET_FIELD_CLASSES = Arrays.asList(NUMERIC_PREDICTOR_FQDN, - CATEGORICAL_PREDICTOR_FQDN); - - private static final String DATADICTIONARY_FQDN = "org.dmg.pmml.DataDictionary"; - - private static final Map REMOVED_LIST_FROM_INSTANTIATION = Map.of(DATADICTIONARY_FQDN, - new RemovedListTuple("addDataFields", JavaType.buildType("org.dmg.pmml.DataField"))); - - - private static final J.Identifier STRING_IDENTIFIER = new J.Identifier(Tree.randomId(), Space.build(" ", Collections.emptyList()), Markers.EMPTY, "String", JavaType.buildType(String.class.getCanonicalName()), null); - - private static final J.Identifier PREDICTOR_GET_FIELD_IDENTIFIER = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, "getField", JavaType.Primitive.String, null); - - - private static final JavaType LIST_JAVA_TYPE = JavaType.buildType(List.class.getCanonicalName()); - - private static final JavaType.Parameterized LIST_GENERIC_JAVA_TYPE = new JavaType.Parameterized(null, (JavaType.FullyQualified) LIST_JAVA_TYPE, List.of(JavaType.GenericTypeVariable.Primitive.String)); - - private static final JavaParser NEW_JPMMLMODEL_JAVAPARSER = getNewJPMMLJavaParser(); - - private final JavaTemplate requireMiningFunctionTemplate = JavaTemplate.builder(this::getCursor, - "@Override\n" + - " public MiningFunction requireMiningFunction() {\n" + - " return null;\n" + - " }\n") - .javaParser(() -> NEW_JPMMLMODEL_JAVAPARSER) - .build(); - - private final JavaTemplate requireMiningSchemaTemplate = JavaTemplate.builder(this::getCursor, - "@Override\n" + - " public MiningSchema requireMiningSchema() {\n" + - " return null;\n" + - " }\n") - .javaParser(() -> NEW_JPMMLMODEL_JAVAPARSER) - .build(); - - - public JPMMLVisitor(String oldInstantiatedFullyQualifiedTypeName, String newInstantiatedFullyQualifiedTypeName) { - this.originalInstantiatedType = JavaType.ShallowClass.build(oldInstantiatedFullyQualifiedTypeName); - this.targetInstantiatedType = JavaType.buildType(newInstantiatedFullyQualifiedTypeName); - } - - - @Override - public J visitBinary(J.Binary binary, ExecutionContext executionContext) { - logger.trace("visitBinary {}", binary); - Expression left = (Expression) super.visitExpression(binary.getLeft(), executionContext); - Expression right = (Expression) super.visitExpression(binary.getRight(), executionContext); - binary = binary - .withLeft(left) - .withRight(right); - return super.visitBinary(binary, executionContext); - } - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { - if (extendsModel(classDecl)) { - classDecl = addMissingMethod(classDecl, "requireMiningFunction", requireMiningFunctionTemplate); - classDecl = addMissingMethod(classDecl, "requireMiningSchema", requireMiningSchemaTemplate); - } - return (J.ClassDeclaration) super.visitClassDeclaration(classDecl, executionContext); - } - - @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) { - logger.trace("visitCompilationUnit {}", cu); - String cuName = cu.getSourcePath().toString(); - boolean toMigrate = toMigrate(cu.getImports()); - if (!toMigrate) { - logger.info("Skipping {}", cuName); - return cu; - } else { - logger.info("Going to migrate {}", cuName); - } - try { - cu = (J.CompilationUnit) super.visitCompilationUnit(cu, executionContext); - maybeAddImport(targetInstantiatedType.toString()); - maybeAddImport(MINING_FUNCTION_NAME_FQDN); - maybeAddImport(MINING_SCHEMA_NAME_FQDN); - maybeRemoveImport(FIELD_NAME_FQDN); - cu = (J.CompilationUnit) new ChangeType(FIELD_NAME_FQDN, String.class.getCanonicalName(), false) - .getVisitor() - .visitCompilationUnit(cu, executionContext); - return cu; - } catch (Throwable t) { - logger.error("Failed to visit {}", cu, t); - return cu; - } - } - - @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { - logger.trace("visitMethodInvocation {}", method); - if (isFieldNameCreate(method)) { - Expression createArgument = method.getArguments().get(0); - createArgument = (Expression) super.visit(createArgument, executionContext); - return createArgument; - } - if (useFieldNameGetValue(method)) { - return method.getSelect(); - } - if (isFieldNameGetNameToGetFieldMapped(method)) { - JavaType.Method methodType = method - .getMethodType() - .withReturnType(JavaType.Primitive.String); - return method - .withName(PREDICTOR_GET_FIELD_IDENTIFIER) - .withMethodType(methodType); - } - if (hasFieldNameParameter(method)) { - JavaType.Method methodType = method.getMethodType() - .withParameterTypes(Collections.singletonList(JavaType.Primitive.String)); - return method.withMethodType(methodType); - } - return super.visitMethodInvocation(method, executionContext); - } - - - @Override - public J visitNewClass(J.NewClass newClass, ExecutionContext executionContext) { - logger.trace("visitNewClass {}", newClass); - J toReturn = replaceInstantiation(newClass); - if (toReturn != newClass) { - return toReturn; - } else { - return super.visitNewClass(newClass, executionContext); - } - } - - @Override - public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext executionContext) { - logger.trace("visitVariable {}", variable); - if (variable.getType() != null && variable.getType().toString().equals(FIELD_NAME_FQDN)) { - variable = variable - .withType(JavaType.Primitive.String) - .withVariableType(variable.getVariableType().withType(JavaType.Primitive.String)); - } - return (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, executionContext); - } - - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, - ExecutionContext executionContext) { - logger.trace("visitVariableDeclarations {}", multiVariable); - multiVariable = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, executionContext); - if (multiVariable.getTypeAsFullyQualified() != null && - multiVariable.getTypeAsFullyQualified().getFullyQualifiedName() != null && - multiVariable.getTypeAsFullyQualified().getFullyQualifiedName().equals(FIELD_NAME_FQDN)) { - multiVariable = multiVariable.withType(JavaType.Primitive.String).withTypeExpression(STRING_IDENTIFIER); - } - return multiVariable; - } - - /** - * Return true if the given J.ClassDeclaration extends it extends org.dmg.pmml.Model - * - * @param classDecl - * @return - */ - protected boolean extendsModel(J.ClassDeclaration classDecl) { - return classDecl.getType() != null && - classDecl.getType().getSupertype() != null && - MODEL_NAME_FQDN.equals(classDecl.getType().getSupertype().getFullyQualifiedName()); - } - - /** - * Return true if the given J.CompilationUnit contains an {@see #FIELD_NAME_FQDN} import, - * false otherwise - * - * @param toCheck - * @return - */ - protected boolean hasFieldNameImport(J.CompilationUnit toCheck) { - return toCheck.getImports().stream().anyMatch(this::isFieldNameImport); - } - - /** - * Return true if the given J.Import is {@see #FIELD_NAME_FQDN}, - * false otherwise - * - * @param toCheck - * @return - */ - protected boolean isFieldNameImport(J.Import toCheck) { - return isSpecificImport(toCheck, FIELD_NAME_FQDN); - } - - /** - * Return true if the given J.Import is fqdn, - * false otherwise - * - * @param toCheck - * @return - */ - protected boolean isSpecificImport(J.Import toCheck, String fqdn) { - return (toCheck.getQualid().getType() instanceof JavaType.Class) && ((JavaType.Class) toCheck.getQualid().getType()).getFullyQualifiedName().equals(fqdn); - } - - /** - * Add a J.MethodDeclaration to the given J.ClassDeclaration if the latter does not contain the searchedMethod, - * otherwise it does nothing - * - * @param classDecl - * @param searchedMethod - * @param javaTemplate - * @return - */ - protected J.ClassDeclaration addMissingMethod(J.ClassDeclaration classDecl, String searchedMethod, JavaTemplate javaTemplate) { - if (methodExists(classDecl, searchedMethod)) { - return classDecl; - } - classDecl = classDecl.withBody( - classDecl.getBody().withTemplate( - javaTemplate, - classDecl.getBody().getCoordinates().lastStatement() - )); - return classDecl; - } - - /** - * Return true if the given J.ClassDeclaration contains the searchedMethod, - * false otherwise - * - * @param toCheck - * @param searchedMethod - * @return - */ - protected boolean methodExists(J.ClassDeclaration toCheck, String searchedMethod) { - return toCheck.getBody().getStatements().stream() - .filter(statement -> statement instanceof J.MethodDeclaration) - .map(J.MethodDeclaration.class::cast) - .anyMatch(methodDeclaration -> methodDeclaration.getName().getSimpleName().equals(searchedMethod)); - } - - /** - * @param newClass - * @return - */ - protected Expression replaceInstantiation(J.NewClass newClass) { - logger.trace("replaceInstantiation {}", newClass); - newClass = replaceOriginalToTargetInstantiation(newClass); - return replaceInstantiationListRemoved(newClass); - } - - /** - * Returns a new J.NewClass with the originalInstantiatedType - * replaced by targetInstantiatedType, if present. - * Otherwise, returns the original newClass. - * - * @param newClass - * @return - */ - protected J.NewClass replaceOriginalToTargetInstantiation(J.NewClass newClass) { - logger.trace("replaceOriginalToTargetInstantiation {}", newClass); - if (newClass.getType() != null && newClass.getType().toString().equals(originalInstantiatedType.toString())) { - JavaType.Method updatedMethod = updateMethodToTargetInstantiatedType(newClass.getConstructorType()); - TypeTree typeTree = updateTypeTreeToTargetInstantiatedType(newClass); - newClass = newClass.withConstructorType(updatedMethod) - .withClazz(typeTree); - } - return newClass; - } - - /** - * Returns a new J.NewClass with the originalInstantiatedType - * replaced by targetInstantiatedType, if present. - * Otherwise, returns the original newClass. - * - * @param newClass - * @return - */ - protected Expression replaceInstantiationListRemoved(J.NewClass newClass) { - logger.trace("replaceInstantiationListRemoved {}", newClass); - Optional optionalRetrieved = getRemovedListTuple(newClass); - if (optionalRetrieved.isPresent()) { - RemovedListTuple removedListTuple = optionalRetrieved.get(); - return removedListTuple.getJMethod(newClass); - } else { - return newClass; - } - } - - /** - * Return Optional<RemovedListTuple> if the given J.NewClass constructor has not the List anymore - * Optional.empty() otherwise - * - * @param toCheck - * @return - */ - protected Optional getRemovedListTuple(J.NewClass toCheck) { - return toCheck.getType() != null && - REMOVED_LIST_FROM_INSTANTIATION.containsKey(toCheck.getType().toString()) && - toCheck.getArguments() != null && - !toCheck.getArguments().isEmpty() - && (toCheck.getArguments().get(0) instanceof J.Identifier) ? Optional.of(REMOVED_LIST_FROM_INSTANTIATION.get(toCheck.getType().toString())) : Optional.empty(); - } - - /** - * Return true if the given J.MethodInvocation is FieldName.create(...), - * false otherwise - * - * @param toCheck - * @return - */ - protected boolean isFieldNameCreate(J.MethodInvocation toCheck) { - return toCheck.getType() != null && toCheck.getType().toString().equals(FIELD_NAME_FQDN) && toCheck.getName().toString().equals("create"); - } - - /** - * Return true if the given J.MethodInvocation is FieldName.create(...), - * false otherwise - * - * @param toCheck - * @return - */ - protected boolean hasFieldNameParameter(J.MethodInvocation toCheck) { - return toCheck.getMethodType() != null && - toCheck.getMethodType().getParameterTypes() != null && - toCheck.getMethodType().getParameterTypes().stream().anyMatch(javaType -> javaType != null && javaType.toString().equals(FIELD_NAME_FQDN)); - } - - /** - * Return true if the given J.MethodInvocation is #FieldName(_any_).getName(...), - * and the modified method is String(_any_).getField(...) - * false otherwise. - * Mapped elements are defined in {@link #GET_NAME_TO_GET_FIELD_CLASSES} - * - * @param toCheck - * @return - */ - protected boolean isFieldNameGetNameToGetFieldMapped(J.MethodInvocation toCheck) { - return toCheck.getMethodType() != null && - toCheck.getMethodType().getDeclaringType() != null && - GET_NAME_TO_GET_FIELD_CLASSES.contains(toCheck.getMethodType().getDeclaringType().toString()) && - toCheck.getName().toString().equals("getName"); - } - - - /** - * Return true if the given J.MethodInvocation invokes (_field_).getValue(), - * false otherwise - * - * @param toCheck - * @return - */ - protected boolean useFieldNameGetValue(J.MethodInvocation toCheck) { - return toCheck.getMethodType() != null && - toCheck.getMethodType().getDeclaringType() != null && - toCheck.getMethodType().getDeclaringType().getFullyQualifiedName() != null && - toCheck.getMethodType().getDeclaringType().getFullyQualifiedName().equals(FIELD_NAME_FQDN) && toCheck.getMethodType().getName().equals("getValue"); - } - - protected boolean toMigrate(List imports) { - return imports.stream() - .anyMatch(anImport -> anImport.getPackageName().startsWith(JPMML_MODEL_PACKAGE_BASE) || - anImport.getPackageName().startsWith(DMG_PMML_MODEL_PACKAGE_BASE)); - } - - protected JavaType.Method updateMethodToTargetInstantiatedType(JavaType.Method oldMethodType) { - if (oldMethodType != null) { - JavaType.Method method = oldMethodType; - method = method.withDeclaringType((JavaType.FullyQualified) targetInstantiatedType) - .withReturnType(targetInstantiatedType); - return method; - } - return null; - } - - protected TypeTree updateTypeTreeToTargetInstantiatedType(J.NewClass newClass) { - return ((J.Identifier) newClass.getClazz()) - .withSimpleName(((JavaType.ShallowClass) targetInstantiatedType).getClassName()) - .withType(targetInstantiatedType); - } - - static class RemovedListTuple { - - private final String addMethodName; - - private final J.Identifier elementIdentifier; - - private final JavaType.Array elementArray; - - private final J.Identifier elementToArrayIdentifier; - private final J.Identifier addMethodIdentifier; - - public RemovedListTuple(String addMethodName, JavaType elementJavaType) { - this.addMethodName = addMethodName; - elementIdentifier = new J.Identifier(Tree.randomId(), Space.build(" ", Collections.emptyList()), Markers.EMPTY, elementJavaType.toString(), elementJavaType, null); - elementArray = new JavaType.Array(null, elementJavaType); - JavaType.Parameterized elementListJavaType = new JavaType.Parameterized(null, (JavaType.FullyQualified) LIST_JAVA_TYPE, List.of(elementJavaType)); - elementToArrayIdentifier = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, "toArray", elementListJavaType, null); - addMethodIdentifier = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, addMethodName, elementListJavaType, null); - } - - public J.MethodInvocation getJMethod(J.NewClass newClass) { - J.Identifier originalListIdentifier = (J.Identifier) newClass.getArguments().get(0); - J.Literal literal = new J.Literal(Tree.randomId(), Space.EMPTY, Markers.EMPTY, 0, "0", null, JavaType.Primitive.Int); - J.ArrayDimension arrayDimension = new J.ArrayDimension(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(literal)); - J.NewArray newArray = new J.NewArray(Tree.randomId(), Space.EMPTY, Markers.EMPTY, elementIdentifier, Collections.singletonList(arrayDimension), null, elementArray); - JavaType.Method methodType = new JavaType.Method(null, 1025, LIST_GENERIC_JAVA_TYPE, "toArray", - elementArray, - Collections.singletonList("arg0"), - Collections.singletonList(elementArray), null, null); - J.MethodInvocation toArrayInvocation = new J.MethodInvocation(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, null, - elementToArrayIdentifier, - JContainer.build(Collections.emptyList()), - methodType) - .withSelect(originalListIdentifier) - .withArguments(Collections.singletonList(newArray)); - JavaType.Method constructorType = newClass.getConstructorType() - .withParameterTypes(Collections.emptyList()) - .withParameterNames(Collections.emptyList()); - J.NewClass noArgClass = newClass.withArguments(Collections.emptyList()) - .withConstructorType(constructorType); - - JavaType.Method addMethodInvocation = new JavaType.Method(null, 1025, - (JavaType.FullyQualified) JavaType.buildType(noArgClass.getType().toString()), - addMethodName, - JavaType.Primitive.Void, - Collections.singletonList("toAdd"), - Collections.singletonList(elementArray), null, null); - - return new J.MethodInvocation(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, null, - addMethodIdentifier, - JContainer.build(Collections.emptyList()), - addMethodInvocation) - .withSelect(noArgClass) - .withArguments(Collections.singletonList(toArrayInvocation)); - } - } - - private static JavaParser getNewJPMMLJavaParser() { - List paths = JavaParser.runtimeClasspath(); - Path newJpmmlModel = getNewJPMMLModelPath(); - paths.add(newJpmmlModel); - return JavaParser.fromJavaVersion() - .classpath(paths) - .logCompilationWarningsAndErrors(true).build(); - } - - public static Path getNewJPMMLModelPath() { - // The new version is expected to have been downloaded by maven plugin at validate phase - File defaultTarget = new File(NEW_JPMML_MAVEN_PATH); - if (!defaultTarget.exists()) { - throw new RuntimeException("Failed to find " + NEW_JPMML_MAVEN_PATH); - } - return defaultTarget.toPath(); - } - -} diff --git a/jpmml-migration-recipe/src/main/resources/META-INF/rewrite/rewrite.yml b/jpmml-migration-recipe/src/main/resources/META-INF/rewrite/rewrite.yml deleted file mode 100644 index 188d842ec0e..00000000000 --- a/jpmml-migration-recipe/src/main/resources/META-INF/rewrite/rewrite.yml +++ /dev/null @@ -1,49 +0,0 @@ -# -# 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. -# - ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.kie.openrewrite.recipe.jpmml.JPMMLRecipe -recipeList: - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: org.jpmml.model.inlinetable.OutputCell - newFullyQualifiedTypeName: org.jpmml.model.cells.OutputCell - ignoreDefinition: true - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: org.jpmml.model.inlinetable.InputCell - newFullyQualifiedTypeName: org.jpmml.model.cells.InputCell - ignoreDefinition: true - - org.kie.openrewrite.recipe.jpmml.JPMMLCodeRecipe: - oldInstantiatedFullyQualifiedTypeName: org.dmg.pmml.ScoreDistribution - newInstantiatedFullyQualifiedTypeName: org.dmg.pmml.ComplexScoreDistribution - - org.openrewrite.maven.ChangePropertyValue: - key: version.org.jpmml.model - newValue: 1.6.4 - -# This creates issue with generic imports like -# -# import org.kie.api.event.rule.*; -# import org.kie.api.event.rule.AgendaGroupPoppedEvent; -# import org.kie.api.event.rule.AgendaGroupPushedEvent; -# import org.kie.api.event.rule.RuleFlowGroupActivatedEvent; -# import org.kie.api.event.rule.RuleFlowGroupDeactivatedEvent; -# -# origin: https://github.com/kiegroup/drools/blob/main/drools-core/src/main/java/org/drools/core/event/DefaultAgendaEventListener.java -# -# - org.openrewrite.java.RemoveUnusedImports \ No newline at end of file diff --git a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/CommonTestingUtilities.java b/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/CommonTestingUtilities.java deleted file mode 100644 index 52d7596ae2c..00000000000 --- a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/CommonTestingUtilities.java +++ /dev/null @@ -1,205 +0,0 @@ -/** - * 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.openrewrite.recipe.jpmml; - -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.Java11Parser; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; - -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; - -public class CommonTestingUtilities { - - private static final List paths = JavaParser.runtimeClasspath(); - - private CommonTestingUtilities() { - } - - public static J.CompilationUnit getCompilationUnitFromClassSource(String classSource) { - JavaParser parser = Java11Parser.builder() - .classpath(paths) - .logCompilationWarningsAndErrors(true) - .build(); - return parser.parse(classSource).get(0); - } - - public static Optional getBinaryFromClassSource(String classSource, - String binaryString) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - TestingVisitor testingVisitor = new TestingVisitor(J.Binary.class, binaryString); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getClassDeclarationFromClassSource(String classSource, - String className) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - TestingVisitor testingVisitor = new TestingVisitor(J.ClassDeclaration.class, className); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getClassDeclarationFromCompilationUnit(J.CompilationUnit compilationUnit, - String className) { - TestingVisitor testingVisitor = new TestingVisitor(J.ClassDeclaration.class, className); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getMethodInvocationFromClassSource(String classSource, - String methodInvocation) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - TestingVisitor testingVisitor = new TestingVisitor(J.MethodInvocation.class, methodInvocation); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getNewClassFromClassSource(String classSource, - String fqdnInstantiatedClass) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - TestingVisitor testingVisitor = new TestingVisitor(J.NewClass.class, fqdnInstantiatedClass); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getVariableDeclarationsFromClassSource(String classSource, - String variableDeclaration) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - TestingVisitor testingVisitor = new TestingVisitor(J.VariableDeclarations.class, variableDeclaration); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getExpressionFromClassSource(String classSource, String expression) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - TestingVisitor testingVisitor = new TestingVisitor(Expression.class, expression); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static Optional getExpressionFromCompilationUnit(J.CompilationUnit compilationUnit, String expression) { - TestingVisitor testingVisitor = new TestingVisitor(Expression.class, expression); - testingVisitor.visit(compilationUnit, getExecutionContext(null)); - return (Optional) testingVisitor.getFoundItem(); - } - - public static List getImportsFromClassSource(String classSource) { - J.CompilationUnit compilationUnit = getCompilationUnitFromClassSource(classSource); - return compilationUnit.getImports(); - } - - - public static ExecutionContext getExecutionContext(Throwable expected) { - return new InMemoryExecutionContext(throwable -> org.assertj.core.api.Assertions.assertThat(throwable).isEqualTo(expected)); - } - - private static class TestingVisitor extends JavaIsoVisitor { - - private final Class SEARCHED_J; - private final String SEARCHED_STRING; - - private Optional foundItem; - - public TestingVisitor(Class SEARCHED_J, String SEARCHED_STRING) { - this.SEARCHED_J = SEARCHED_J; - this.SEARCHED_STRING = SEARCHED_STRING; - foundItem = Optional.empty(); - } - - public Optional getFoundItem() { - return foundItem; - } - - @Override - public J.Binary visitBinary(J.Binary binary, ExecutionContext executionContext) { - if (SEARCHED_J.equals(J.Binary.class) && binary.toString().equals(SEARCHED_STRING)) { - foundItem = Optional.of(binary); - return binary; - } else { - return super.visitBinary(binary, executionContext); - } - } - - @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) { - if (SEARCHED_J.equals(J.CompilationUnit.class)) { - foundItem = Optional.of(cu); - return cu; - } else { - return super.visitCompilationUnit(cu, executionContext); - } - } - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { - if (SEARCHED_J.equals(J.ClassDeclaration.class) && classDecl.getSimpleName().equals(SEARCHED_STRING)) { - foundItem = Optional.of(classDecl); - return classDecl; - } else { - return super.visitClassDeclaration(classDecl, executionContext); - } - } - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { - if (SEARCHED_J.equals(J.MethodInvocation.class) && method.toString().startsWith(SEARCHED_STRING + "(")) { - foundItem = Optional.of(method); - return method; - } else { - return super.visitMethodInvocation(method, executionContext); - } - } - - @Override - public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionContext) { - if (SEARCHED_J.equals(J.NewClass.class) && newClass.getType().toString().equals(SEARCHED_STRING)) { - foundItem = Optional.of(newClass); - return newClass; - } else { - return super.visitNewClass(newClass, executionContext); - } - } - - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) { - if (SEARCHED_J.equals(J.VariableDeclarations.class) && multiVariable.toString().startsWith(SEARCHED_STRING)) { - foundItem = Optional.of(multiVariable); - return multiVariable; - } else { - return super.visitVariableDeclarations(multiVariable, executionContext); - } - } - - @Override - public Expression visitExpression(Expression expression, ExecutionContext executionContext) { - if (SEARCHED_J.equals(Expression.class) && expression.toString().equals(SEARCHED_STRING)) { - foundItem = Optional.of(expression); - return expression; - } else { - return super.visitExpression(expression, executionContext); - } - } - } -} diff --git a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLCodeRecipeTest.java b/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLCodeRecipeTest.java deleted file mode 100644 index ea72a3e8c68..00000000000 --- a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLCodeRecipeTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * 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.openrewrite.recipe.jpmml; - -import org.intellij.lang.annotations.Language; -import org.junit.jupiter.api.Test; -import org.openrewrite.java.Assertions; -import org.openrewrite.java.Java11Parser; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import java.nio.file.Path; -import java.util.List; - -class JPMMLCodeRecipeTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - List paths = JavaParser.runtimeClasspath(); - spec.recipe(new JPMMLCodeRecipe("org.dmg.pmml.ScoreDistribution", - "org.dmg.pmml.ComplexScoreDistribution")); - spec.parser(Java11Parser.builder() - .classpath(paths) - .logCompilationWarningsAndErrors(true)); - } - - @Test - void removeFieldNameCreate() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.FieldName;\n" + - "class FooBar {\n" + - "static void method() {\n" + - "System.out.println(FieldName.create(\"OUTPUT_\"));\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "class FooBar {\n" + - "static void method() {\n" + - "System.out.println(\"OUTPUT_\");\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void changeInstantiation_ScoreDistribution() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "class FooBar {\n" + - "static void method() {\n" + - "ScoreDistribution toReturn = new ScoreDistribution();\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "import org.dmg.pmml.ComplexScoreDistribution;\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "\n" + - "class FooBar {\n" + - "static void method() {\n" + - "ScoreDistribution toReturn = new ComplexScoreDistribution();\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void changeInstantiation_DataDictionary() { - @Language("java") - String before = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "static void method(List dataFields) {\n" + - "DataDictionary dataDictionary = new DataDictionary(dataFields);\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "static void method(List dataFields) {\n" + - "DataDictionary dataDictionary = new DataDictionary().addDataFields(dataFields.toArray(new org.dmg.pmml.DataField[0]));\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void changeUsage_FieldNameCreateWithBinary() { - @Language("java") - String before = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "public class Stub {\n" + - " \n" + - " public void hello(DataField dataField) {\n" + - " System.out.println(FieldName.create(\"OUTPUT_\" + dataField.getName().getValue()));\n" + - " }\n" + - "\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "public class Stub {\n" + - " \n" + - " public void hello(DataField dataField) {\n" + - " System.out.println(\"OUTPUT_\" +dataField.getName());\n" + - " }\n" + - "\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - -} \ No newline at end of file diff --git a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLRecipeTest.java b/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLRecipeTest.java deleted file mode 100644 index f8ea02630d1..00000000000 --- a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLRecipeTest.java +++ /dev/null @@ -1,424 +0,0 @@ -/** - * 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.openrewrite.recipe.jpmml; - -import org.intellij.lang.annotations.Language; -import org.junit.jupiter.api.Test; -import org.openrewrite.java.Assertions; -import org.openrewrite.java.Java11Parser; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import java.io.InputStream; -import java.nio.file.Path; -import java.util.List; - -import static org.kie.openrewrite.recipe.jpmml.JPMMLVisitor.getNewJPMMLModelPath; - - -public class JPMMLRecipeTest implements RewriteTest { - - private static final String JPMML_RECIPE_NAME = "org.kie.openrewrite.recipe.jpmml.JPMMLRecipe"; - - @Override - public void defaults(RecipeSpec spec) { - List paths = JavaParser.runtimeClasspath(); - Path newJpmmlModel = getNewJPMMLModelPath(); - paths.add(newJpmmlModel); - try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/rewrite/rewrite.yml")) { - assert inputStream != null; - spec.recipe(inputStream, JPMML_RECIPE_NAME); - spec.parser(Java11Parser.builder() - .classpath(paths) - .logCompilationWarningsAndErrors(true) - ); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Test - public void changeInstantiation_DataDictionary() { - @Language("java") - String before = "package com.yourorg;\n" + - "\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public String hello(List dataFields) {\n" + - " new DataDictionary(dataFields);\n" + - " return \"Hello from com.yourorg.FooLol!\";\n" + - " }\n" + - "\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public String hello(List dataFields) {\n" + - " new DataDictionary().addDataFields(dataFields.toArray(new org.dmg.pmml.DataField[0]));\n" + - " return \"Hello from com.yourorg.FooLol!\";\n" + - " }\n" + - "\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void addMissingMethods_Model() { - @Language("java") - String before = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.LocalTransformations;\n" + - "import org.dmg.pmml.MathContext;\n" + - "import org.dmg.pmml.MiningFunction;\n" + - "import org.dmg.pmml.MiningSchema;\n" + - "import org.dmg.pmml.Model;\n" + - "import org.dmg.pmml.Visitor;\n" + - "import org.dmg.pmml.VisitorAction;\n" + - "\n" + - "public class SubModel extends Model {\n" + - " @Override\n" + - " public String getModelName() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setModelName(String modelName) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MiningFunction getMiningFunction() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setMiningFunction(MiningFunction miningFunction) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public String getAlgorithmName() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setAlgorithmName(String algorithmName) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public boolean isScorable() {\n" + - " return false;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setScorable(Boolean scorable) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MathContext getMathContext() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setMathContext(MathContext mathContext) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MiningSchema getMiningSchema() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setMiningSchema(MiningSchema miningSchema) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public LocalTransformations getLocalTransformations() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setLocalTransformations(LocalTransformations localTransformations) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public VisitorAction accept(Visitor visitor) {\n" + - " return null;\n" + - " }\n" + - "}\n"; - @Language("java") - String after = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.LocalTransformations;\n" + - "import org.dmg.pmml.MathContext;\n" + - "import org.dmg.pmml.MiningFunction;\n" + - "import org.dmg.pmml.MiningSchema;\n" + - "import org.dmg.pmml.Model;\n" + - "import org.dmg.pmml.Visitor;\n" + - "import org.dmg.pmml.VisitorAction;\n" + - "\n" + - "public class SubModel extends Model {\n" + - " @Override\n" + - " public String getModelName() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setModelName(String modelName) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MiningFunction getMiningFunction() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setMiningFunction(MiningFunction miningFunction) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public String getAlgorithmName() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setAlgorithmName(String algorithmName) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public boolean isScorable() {\n" + - " return false;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setScorable(Boolean scorable) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MathContext getMathContext() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setMathContext(MathContext mathContext) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MiningSchema getMiningSchema() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setMiningSchema(MiningSchema miningSchema) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public LocalTransformations getLocalTransformations() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public Model setLocalTransformations(LocalTransformations localTransformations) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public VisitorAction accept(Visitor visitor) {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MiningFunction requireMiningFunction() {\n" + - " return null;\n" + - " }\n" + - "\n" + - " @Override\n" + - " public MiningSchema requireMiningSchema() {\n" + - " return null;\n" + - " }\n" + - "}\n"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void changeImports() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.jpmml.model.inlinetable.InputCell;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "InputCell input = null;\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "\n" + - "import org.jpmml.model.cells.InputCell;\n" + - "\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "InputCell input = null;\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void changeFieldNameVariableDeclaration() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.FieldName;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "FieldName fieldName = FieldName.create(\"OUTPUT_\");\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - " String fieldName =\"OUTPUT_\";\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void changeFieldNameVariableNull() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.FieldName;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "FieldName fieldName = null;\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - " String fieldName = null;\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void removeFieldNameCreate() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.FieldName;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "System.out.println(FieldName.create(\"OUTPUT_\"));\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "System.out.println(\"OUTPUT_\");\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - @Test - public void removeFieldNameGetValue() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.OutputField;\n" + - "class FooBar {\n" + - "static public void method(OutputField toConvert) {\n" + - "final String name = toConvert.getName() != null ? toConvert.getName().getValue() : null;\n" + - "}\n" + - "}"; - String after = "package com.yourorg;\n" + - "import org.dmg.pmml.OutputField;\n" + - "class FooBar {\n" + - "static public void method(OutputField toConvert) {\n" + - "final String name = toConvert.getName() != null ?toConvert.getName() : null;\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - - - @Test - public void changeInstantiation_ScoreDistribution() { - @Language("java") - String before = "package com.yourorg;\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "ScoreDistribution toReturn = new ScoreDistribution();\n" + - "}\n" + - "}"; - @Language("java") - String after = "package com.yourorg;\n" + - "import org.dmg.pmml.ComplexScoreDistribution;\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "\n" + - "class FooBar {\n" + - "static public void method() {\n" + - "ScoreDistribution toReturn = new ComplexScoreDistribution();\n" + - "}\n" + - "}"; - rewriteRun( - Assertions.java(before, after) - ); - } - -} \ No newline at end of file diff --git a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLVisitorTest.java b/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLVisitorTest.java deleted file mode 100644 index 8fd003771c7..00000000000 --- a/jpmml-migration-recipe/src/test/java/org/kie/openrewrite/recipe/jpmml/JPMMLVisitorTest.java +++ /dev/null @@ -1,1148 +0,0 @@ -/** - * 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.openrewrite.recipe.jpmml; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeTree; - -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.kie.openrewrite.recipe.jpmml.CommonTestingUtilities.*; - -class JPMMLVisitorTest { - - private JPMMLVisitor jpmmlVisitor; - - @BeforeEach - public void init() { - jpmmlVisitor = new JPMMLVisitor("org.dmg.pmml.ScoreDistribution", "org.dmg.pmml.ComplexScoreDistribution"); - } - - @Test - public void visitBinary_StringFieldNameGetValue() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "class Stub {\n" + - " public void hello(DataField dataField) {\n" + - " FieldName.create(\"DER_\" + dataField.getName().getValue());\n" + - " }\n" + - "}"; - String binary = "\"DER_\" + dataField.getName().getValue()"; - J.Binary toTest = getBinaryFromClassSource(classTested, binary) - .orElseThrow(() -> new RuntimeException("Failed to find J.Binary " + binary)); - J retrieved = jpmmlVisitor.visitBinary(toTest, getExecutionContext(null)); - String expected = "\"DER_\" +dataField.getName()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.Binary.class) - .hasToString(expected); - } - - @Test - public void visitBinary_FieldNameGetValueString() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "class Stub {\n" + - " public void hello(DataField dataField) {\n" + - " FieldName.create(dataField.getName().getValue() + \"DER_\");\n" + - " }\n" + - "}"; - String binary = "dataField.getName().getValue() + \"DER_\""; - J.Binary toTest = getBinaryFromClassSource(classTested, binary) - .orElseThrow(() -> new RuntimeException("Failed to find J.Binary " + binary)); - J retrieved = jpmmlVisitor.visitBinary(toTest, getExecutionContext(null)); - String expected = "dataField.getName() + \"DER_\""; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.Binary.class) - .hasToString(expected); - } - - @Test - public void visitBinary_FieldNameGetValueFieldNameGetValue() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "class Stub {\n" + - " public void hello(DataField dataField) {\n" + - " FieldName.create(dataField.getName().getValue() + dataField.getName().getValue());\n" + - " }\n" + - "}"; - String binary = "dataField.getName().getValue() + dataField.getName().getValue()"; - J.Binary toTest = getBinaryFromClassSource(classTested, binary) - .orElseThrow(() -> new RuntimeException("Failed to find J.Binary " + binary)); - J retrieved = jpmmlVisitor.visitBinary(toTest, getExecutionContext(null)); - String expected = "dataField.getName() +dataField.getName()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.Binary.class) - .hasToString(expected); - } - - @Test - public void visitMethodInvocation_NumericPredictorGetName() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.NumericPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String bye(NumericPredictor numericPredictor) {\n" + - " FieldName fieldName = numericPredictor.getName();\n" + - " return fieldName.getValue();\n" + - " }" + - "}"; - String methodTested = "numericPredictor.getName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation numericPredictor.getName()")); - assertThat(toTest).isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "numericPredictor.getField()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void visitMethodInvocation_CategoricalPredictorGetName() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.CategoricalPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String bye(CategoricalPredictor categoricalPredictor) {\n" + - " FieldName fieldName = categoricalPredictor.getName();\n" + - " return fieldName.getValue();\n" + - " }" + - "}"; - String methodTested = "categoricalPredictor.getName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation categoricalPredictor.getName()")); - assertThat(toTest).isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "categoricalPredictor.getField()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void visitMethodInvocation_FieldNameCreate() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "class Stub {\n" + - " public String hello() {\n" + - " System.out.println(FieldName.create(\"OUTPUT_\"));\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String expressionTested = "FieldName.create"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, expressionTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation FieldName.create(\"OUTPUT_\")")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "OUTPUT_"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.Literal.class); - assertThat(((J.Literal) retrieved).getValue()).isEqualTo(expected); - } - - @Test - public void visitMethodInvocation_FieldNameCreateWithBinary() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "class Stub {\n" + - " public String hello(DataField dataField) {\n" + - " System.out.println(FieldName.create(\"OUTPUT_\" + dataField.getName().getValue()));\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String expressionTested = "System.out.println"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, expressionTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation System.out.println(FieldName.create(\"OUTPUT_\" + dataField.getName().getValue()))")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "System.out.println(\"OUTPUT_\" +dataField.getName())"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class); - assertThat(((J.MethodInvocation) retrieved)).hasToString(expected); - } - - @Test - public void visitMethodInvocation_AccessFieldName() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.DataType;\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.MiningField;\n" + - "import org.dmg.pmml.mining.MiningModel;\n" + - "import org.dmg.pmml.OpType;\n" + - "import org.dmg.pmml.OutputField;\n" + - "import org.dmg.pmml.Target;\n" + - "\n" + - "class Stub {\n" + - " public String bye() {\n" + - " MiningField toReturn = new MiningField(FieldName.create(new String(\"TestingFIeld\")));\n" + - " OutputField toConvert = new OutputField(FieldName.create(\"FIELDNAME\"), OpType.CATEGORICAL," + - " DataType.BOOLEAN);\n" + - " final String name = toConvert.getName() != null ? toConvert.getName().getValue() : null;\n" + - " Target target = new Target();\n" + - " String field = target.getField().getValue();\n" + - " String key = target.getKey().getValue();\n" + - " return name;\n" + - " }" + - "}"; - String methodTested = "toConvert.getName().getValue"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation toConvert.getName().getValue")); - assertThat(toTest).isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "toConvert.getName()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - - methodTested = "target.getField().getValue"; - toTest = getMethodInvocationFromClassSource(classTested, methodTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation target.getField().getValue")); - assertThat(toTest).isNotNull(); - retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - expected = "target.getField()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - - methodTested = "target.getKey().getValue"; - toTest = getMethodInvocationFromClassSource(classTested, methodTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation target.getKey().getValue")); - assertThat(toTest).isNotNull(); - retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - expected = "target.getKey()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void visitMethodInvocation_FieldNameGetValue() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import java.util.List;\n" + - "import java.util.Objects;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "import org.dmg.pmml.Field;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " private List> fields;\n" + - "\n" + - " public void bye() {\n" + - " DataField targetDataField = this.fields.stream()\n" + - " .filter(DataField.class::isInstance)\n" + - " .map(DataField.class::cast)\n" + - " .filter(field -> Objects.equals(getTargetFieldName(), field.getName().getValue()))\n" + - " .findFirst().orElse(null);\n" + - " }\n" + - " public String getTargetFieldName() {\n" + - " return \"targetDataFieldName\";\n" + - " }\n" + - "}"; - String expressionTested = "field.getName().getValue"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, expressionTested) - .orElseThrow(() -> new RuntimeException("Failed to find Expression FieldName.create(\"OUTPUT_\")")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "field.getName()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void visitMethodInvocation_FieldNameGetNameToGetFieldMapped() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.CategoricalPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String hello(CategoricalPredictor categoricalPredictor) {\n" + - " FieldName fieldName = categoricalPredictor.getName();\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String methodInvocation = "categoricalPredictor.getName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation categoricalPredictor.getName()")); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - String expected = "categoricalPredictor.getField()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void visitMethodInvocation_HasFieldNameParameter() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "import org.dmg.pmml.DerivedField;\n" + - "import java.util.Objects;\n" + - "\n" + - "class Stub {\n" + - " public void hello(DataField dataField) {\n" + - " DerivedField toReturn = new DerivedField();\n" + - " toReturn.setName(FieldName.create(\"DER_\" + dataField.getName().getValue()));\n" + - " }\n" + - "}"; - String methodInvocation = "toReturn.setName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to toReturn.setName")); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitMethodInvocation(toTest, executionContext); - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class); - String expected = "String"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class); - assertThat(((J.MethodInvocation)retrieved).getMethodType().getParameterTypes().get(0)) - .hasToString(expected); - } - - @Test - public void visitNewClass_FieldNameCreate() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.MiningField;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public String hello() {\n" + - " MiningField toReturn = new MiningField(FieldName.create(new String(\"TestingField\")));\n" + - " return \"Hello from com.yourorg.FooLol!\";\n" + - " }\n" + - "\n" + - "}"; - String classInstantiated = "org.dmg.pmml.MiningField"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.MiningField")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitNewClass(toTest, executionContext); - String expected = "new MiningField(new String(\"TestingField\"))"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.NewClass.class) - .hasToString(expected); - } - - @Test - public void visitNewClass_AccessFieldNameInsideConstructor() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.Target;\n" + - "\n" + - "class Stub {\n" + - " public String bye() {\n" + - " Target target = new Target();\n" + - " String name = new String(target.getKey().getValue());\n" + - " return name;\n" + - " }" + - "}"; - String classInstantiated = "java.lang.String"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass java.lang.String")); - assertThat(toTest).isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitNewClass(toTest, executionContext); - String expected = "new String(target.getKey())"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.NewClass.class) - .hasToString(expected); - } - - @Test - public void visitNewClass_ScoreDistribution() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public String hello() {\n" + - " ScoreDistribution scoreDistribution = new ScoreDistribution();\n" + - " return \"Hello from com.yourorg.FooLol!\";\n" + - " }\n" + - "\n" + - "}"; - String classInstantiated = "org.dmg.pmml.ScoreDistribution"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.ScoreDistribution")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitNewClass(toTest, executionContext); - String expected = "new ComplexScoreDistribution()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.NewClass.class) - .hasToString(expected); - } - - @Test - public void visitNewClass_DataDictionary() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public String hello(List dataFields) {\n" + - " DataDictionary dataDictionary = new DataDictionary(dataFields);\n" + - " return \"Hello from com.yourorg.FooLol!\";\n" + - " }\n" + - "\n" + - "}"; - String classInstantiated = "org.dmg.pmml.DataDictionary"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.DataDictionary")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J retrieved = jpmmlVisitor.visitNewClass(toTest, executionContext); - String expected = "new DataDictionary().addDataFields(dataFields.toArray(new org.dmg.pmml.DataField[0]))"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void visitVariableDeclarations_AccessFieldNameAsSecondParameter() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import java.util.List;\n" + - "import java.util.Objects;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "import org.dmg.pmml.Field;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " private List> fields;\n" + - "\n" + - " public void bye() {\n" + - " DataField targetDataField = this.fields.stream()\n" + - " .filter(DataField.class::isInstance)\n" + - " .map(DataField.class::cast)\n" + - " .filter(field -> Objects.equals(getTargetFieldName(), field.getName().getValue()))\n" + - " .findFirst().orElse(null);\n" + - " }\n" + - " public String getTargetFieldName() {\n" + - " return \"targetDataFieldName\";\n" + - " }\n" + - "}"; - String variableDeclaration = "DataField targetDataField = "; - J.VariableDeclarations toTest = getVariableDeclarationsFromClassSource(classTested, variableDeclaration) - .orElseThrow(() -> new RuntimeException("Failed to find J.VariableDeclarations DataField targetDataField = ")); - assertThat(toTest).isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J.VariableDeclarations retrieved = jpmmlVisitor.visitVariableDeclarations(toTest, executionContext); - String expected = "DataField targetDataField = this.fields.stream()\n" + - " .filter(DataField.class::isInstance)\n" + - " .map(DataField.class::cast)\n" + - " .filter(field -> Objects.equals(getTargetFieldName(),field.getName()))\n" + - " .findFirst().orElse(null)"; - assertThat(retrieved).isNotNull() - .hasToString(expected); - } - - @Test - public void visitVariableDeclarations_FieldName() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "class Stub {\n" + - " public String hello() {\n" + - " FieldName fieldName = FieldName.create(\"OUTPUT_\");\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String variableDeclaration = "FieldName fieldName = FieldName.create(\"OUTPUT_\")"; - J.VariableDeclarations toTest = getVariableDeclarationsFromClassSource(classTested, variableDeclaration) - .orElseThrow(() -> new RuntimeException("Failed to find J.VariableDeclarations FieldName fieldName = FieldName.create(\"OUTPUT_\")")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J.VariableDeclarations retrieved = jpmmlVisitor.visitVariableDeclarations(toTest, executionContext); - String expected = "String fieldName =\"OUTPUT_\""; - assertThat(retrieved) - .isNotNull() - .hasToString(expected); - } - - @Test - public void visitVariableDeclarations_CategoricalPredictorGetName() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.CategoricalPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String hello(CategoricalPredictor categoricalPredictor) {\n" + - " FieldName fieldName = categoricalPredictor.getName();\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String variableDeclaration = "FieldName fieldName = categoricalPredictor.getName()"; - J.VariableDeclarations toTest = getVariableDeclarationsFromClassSource(classTested, variableDeclaration) - .orElseThrow(() -> new RuntimeException("Failed to find J.VariableDeclarations FieldName fieldName = categoricalPredictor.getName()")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J.VariableDeclarations retrieved = jpmmlVisitor.visitVariableDeclarations(toTest, executionContext); - String expected = "String fieldName = categoricalPredictor.getField()"; - assertThat(retrieved) - .isNotNull() - .hasToString(expected); - } - - @Test - public void visitVariableDeclarations_NumericPredictorGetName() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.NumericPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String hello(NumericPredictor numericPredictor) {\n" + - " FieldName fieldName = numericPredictor.getName();\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String variableDeclaration = "FieldName fieldName = numericPredictor.getName()"; - J.VariableDeclarations toTest = getVariableDeclarationsFromClassSource(classTested, variableDeclaration) - .orElseThrow(() -> new RuntimeException("Failed to find J.VariableDeclarations FieldName fieldName = FieldName.create(\"OUTPUT_\")")); - assertThat(toTest) - .isNotNull(); - ExecutionContext executionContext = getExecutionContext(null); - J.VariableDeclarations retrieved = jpmmlVisitor.visitVariableDeclarations(toTest, executionContext); - String expected = "String fieldName = numericPredictor.getField()"; - assertThat(retrieved) - .isNotNull() - .hasToString(expected); - } - - @Test - public void hasFieldNameImport_true() { - String classTested = "package com.yourorg;\n" + - "import org.dmg.pmml.FieldName;\n" + - "import java.util.List;\n" + - "class FooBar {\n" + - "};"; - J.CompilationUnit toTest = getCompilationUnitFromClassSource(classTested); - assertThat(jpmmlVisitor.hasFieldNameImport(toTest)) - .isTrue(); - } - - @Test - public void hasFieldNameImport_false() { - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "};"; - J.CompilationUnit toTest = getCompilationUnitFromClassSource(classTested); - assertThat(jpmmlVisitor.hasFieldNameImport(toTest)) - .isFalse(); - } - - @Test - public void isFieldNameImport_true() { - String classTested = "package com.yourorg;\n" + - "import org.dmg.pmml.FieldName;\n" + - "class FooBar {\n" + - "};"; - J.Import toTest = getImportsFromClassSource(classTested).get(0); - assertThat(jpmmlVisitor.isFieldNameImport(toTest)) - .isTrue(); - } - - @Test - public void isFieldNameImport_false() { - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "class FooBar {\n" + - "};"; - J.Import toTest = getImportsFromClassSource(classTested).get(0); - assertThat(jpmmlVisitor.isFieldNameImport(toTest)) - .isFalse(); - } - - @Test - public void addMissingMethod_Add() { - String classTested = "package com.yourorg;\n" + - "\n" + - "\n" + - "class Stub {\n" + - " public void hello() {\n" + - " System.out.println(\"Hello\");\n" + - " }\n" + - "}"; - String className = "Stub"; - J.CompilationUnit cu = getCompilationUnitFromClassSource(classTested); - J.ClassDeclaration toTest = getClassDeclarationFromCompilationUnit(cu, className) - .orElseThrow(() -> new RuntimeException("Failed to find J.ClassDeclaration Stub")); - Cursor cursor = new Cursor(jpmmlVisitor.getCursor(), cu); - JavaTemplate requireMiningSchemaTemplate = JavaTemplate.builder(() -> cursor, - " public boolean requireMiningSchema() {\n" + - " return null;\n" + - " }\n") - .build(); - J.ClassDeclaration retrieved = jpmmlVisitor.addMissingMethod(toTest, "requireMiningSchema", requireMiningSchemaTemplate); - assertThat(retrieved) - .isEqualTo(toTest); - assertThat(jpmmlVisitor.methodExists(retrieved, "requireMiningSchema")) - .isTrue(); - } - - @Test - public void addMissingMethod_NotAdd() { - String classTested = "package com.yourorg;\n" + - "\n" + - "\n" + - "class Stub {\n" + - " public void hello() {\n" + - " System.out.println(\"Hello\");\n" + - " }\n" + - "}"; - String className = "Stub"; - J.ClassDeclaration toTest = getClassDeclarationFromClassSource(classTested, className) - .orElseThrow(() -> new RuntimeException("Failed to find J.ClassDeclaration Stub")); - assertThat(jpmmlVisitor.addMissingMethod(toTest, "hello", null)) - .isEqualTo(toTest); - } - - @Test - public void methodExists_true() { - String classTested = "package com.yourorg;\n" + - "\n" + - "\n" + - "class Stub {\n" + - " public void hello() {\n" + - " System.out.println(\"Hello\");\n" + - " }\n" + - "}"; - String className = "Stub"; - J.ClassDeclaration toTest = getClassDeclarationFromClassSource(classTested, className) - .orElseThrow(() -> new RuntimeException("Failed to find J.ClassDeclaration Stub")); - assertThat(jpmmlVisitor.methodExists(toTest, "hello")) - .isTrue(); - } - - @Test - public void methodExists_false() { - String classTested = "package com.yourorg;\n" + - "\n" + - "\n" + - "class Stub {\n" + - " public void hello() {\n" + - " System.out.println(\"Hello\");\n" + - " }\n" + - "}"; - String className = "Stub"; - J.ClassDeclaration toTest = getClassDeclarationFromClassSource(classTested, className) - .orElseThrow(() -> new RuntimeException("Failed to find J.ClassDeclaration Stub")); - assertThat(jpmmlVisitor.methodExists(toTest, "notHello")) - .isFalse(); - } - - @Test - public void replaceOriginalToTargetInstantiation_replaced() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public String hello() {\n" + - " ScoreDistribution scoreDistribution = new ScoreDistribution();\n" + - " return \"Hello from com.yourorg.FooLol!\";\n" + - " }\n" + - "\n" + - "}"; - String classInstantiated = "org.dmg.pmml.ScoreDistribution"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.ScoreDistribution")); - assertThat(toTest) - .isNotNull(); - J.NewClass retrieved = jpmmlVisitor.replaceOriginalToTargetInstantiation(toTest); - String expected = "new ComplexScoreDistribution()"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.NewClass.class) - .hasToString(expected); - } - - @Test - public void replaceOriginalToTargetInstantiation_notReplaced() { - String classTested = "package com.yourorg;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "static void method() {\n" + - " DataField dataField = new DataField();\n" + - "}\n" + - "}"; - String instantiatedClass = "org.dmg.pmml.DataField"; - J.NewClass toTest = getNewClassFromClassSource(classTested, instantiatedClass) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.DataField")); - assertThat(toTest) - .isNotNull(); - Expression retrieved = jpmmlVisitor.replaceOriginalToTargetInstantiation(toTest); - assertThat(retrieved) - .isNotNull() - .isEqualTo(toTest); - } - - @Test - public void replaceInstantiationListRemoved_replaced() { - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "static void method(List dataFields) {\n" + - "DataDictionary dataDictionary = new DataDictionary(dataFields);\n" + - "}\n" + - "}"; - String instantiatedClass = "org.dmg.pmml.DataDictionary"; - J.NewClass toTest = getNewClassFromClassSource(classTested, instantiatedClass) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.DataDictionary")); - assertThat(toTest) - .isNotNull(); - Expression retrieved = jpmmlVisitor.replaceInstantiationListRemoved(toTest); - String expected = "new DataDictionary().addDataFields(dataFields.toArray(new org.dmg.pmml.DataField[0]))"; - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.MethodInvocation.class) - .hasToString(expected); - } - - @Test - public void replaceInstantiationListRemoved_notReplaced() { - String classTested = "package com.yourorg;\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "class FooBar {\n" + - "static void method() {\n" + - " ScoreDistribution scoreDistribution = new ScoreDistribution();\n" + - "}\n" + - "}"; - String instantiatedClass = "org.dmg.pmml.ScoreDistribution"; - J.NewClass toTest = getNewClassFromClassSource(classTested, instantiatedClass) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.DataDictionary")); - assertThat(toTest) - .isNotNull(); - Expression retrieved = jpmmlVisitor.replaceInstantiationListRemoved(toTest); - assertThat(retrieved) - .isNotNull() - .isEqualTo(toTest); - } - - @Test - public void getRemovedListTuple_present() { - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "static void method(List dataFields) {\n" + - "DataDictionary dataDictionary = new DataDictionary(dataFields);\n" + - "}\n" + - "}"; - String instantiatedClass = "org.dmg.pmml.DataDictionary"; - J.NewClass toTest = getNewClassFromClassSource(classTested, instantiatedClass) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.DataDictionary")); - assertThat(toTest) - .isNotNull(); - assertThat(jpmmlVisitor.getRemovedListTuple(toTest)) - .isPresent(); - } - - @Test - public void getRemovedListTuple_notPresent() { - String classTested = "package com.yourorg;\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "class FooBar {\n" + - "static void method() {\n" + - " ScoreDistribution scoreDistribution = new ScoreDistribution();\n" + - "}\n" + - "}"; - String instantiatedClass = "org.dmg.pmml.ScoreDistribution"; - J.NewClass toTest = getNewClassFromClassSource(classTested, instantiatedClass) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.ScoreDistribution")); - assertThat(toTest) - .isNotNull(); - assertThat(jpmmlVisitor.getRemovedListTuple(toTest)) - .isNotPresent(); - } - - @Test - public void isFieldNameCreate_true() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "class Stub {\n" + - " public void hello() {\n" + - " System.out.println(FieldName.create(\"OUTPUT_\"));\n" + - " }\n" + - "}"; - String expressionTested = "FieldName.create"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, expressionTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation FieldName.create(\"OUTPUT_\")")); - assertThat(toTest) - .isNotNull(); - assertThat(jpmmlVisitor.isFieldNameCreate(toTest)) - .isTrue(); - } - - @Test - public void isFieldNameCreate_false() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "class Stub {\n" + - " public void hello() {\n" + - " System.out.println(FieldName.create(\"OUTPUT_\"));\n" + - " }\n" + - "}"; - String expressionTested = "System.out.println"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, expressionTested) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation System.out.println(FieldName.create(\"OUTPUT_\"))")); - assertThat(toTest) - .isNotNull(); - assertThat(jpmmlVisitor.isFieldNameCreate(toTest)) - .isFalse(); - } - - @Test - public void hasFieldNameParameter_true() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "import org.dmg.pmml.DerivedField;\n" + - "import java.util.Objects;\n" + - "\n" + - "class Stub {\n" + - " public void hello(DataField dataField) {\n" + - " DerivedField toReturn = new DerivedField();\n" + - " toReturn.setName(FieldName.create(\"DER_\" + dataField.getName().getValue()));\n" + - " }\n" + - "}"; - String methodInvocation = "toReturn.setName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to toReturn.setName")); - assertThat(jpmmlVisitor.hasFieldNameParameter(toTest)) - .isTrue(); - } - - @Test - public void hasFieldNameParameter_false() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "import java.util.Objects;\n" + - "\n" + - "class Stub {\n" + - " public void hello(DataField field) {\n" + - " Objects.equals(null, field.getName().getValue());\n" + - " }\n" + - "}"; - String methodInvocation = "Objects.equals"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find Objects.equals")); - assertThat(jpmmlVisitor.hasFieldNameParameter(toTest)) - .isFalse(); - classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import java.util.Objects;\n" + - "\n" + - "class Stub {\n" + - " public void hello(FieldName fieldName) {\n" + - " Objects.equals(null, fieldName.getValue());\n" + - " }\n" + - "}"; - toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation numericPredictor.getName()")); - assertThat(jpmmlVisitor.hasFieldNameParameter(toTest)) - .isFalse(); - } - - @Test - public void isFieldNameGetNameToGetFieldMapped_true() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.CategoricalPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String hello(CategoricalPredictor categoricalPredictor) {\n" + - " FieldName fieldName = categoricalPredictor.getName();\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String methodInvocation = "categoricalPredictor.getName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation categoricalPredictor.getName()")); - assertThat(jpmmlVisitor.isFieldNameGetNameToGetFieldMapped(toTest)).isTrue(); - classTested ="package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.regression.NumericPredictor;\n" + - "\n" + - "class Stub {\n" + - " public String hello(NumericPredictor numericPredictor) {\n" + - " FieldName fieldName = numericPredictor.getName();\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - methodInvocation = "numericPredictor.getName"; - toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation numericPredictor.getName()")); - assertThat(jpmmlVisitor.isFieldNameGetNameToGetFieldMapped(toTest)).isTrue(); - } - - @Test - public void isFieldNameGetNameToGetFieldMapped_false() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "class Stub {\n" + - " public String hello(DataField dataField) {\n" + - " FieldName fieldName = dataField.getName();\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String methodInvocation = "dataField.getName"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation dataField.getName()")); - assertThat(jpmmlVisitor.isFieldNameGetNameToGetFieldMapped(toTest)).isFalse(); - } - - @Test - public void useFieldNameGetValue_true() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "class Stub {\n" + - " public String hello(DataField field) {\n" + - " System.out.println(field.getName().getValue());\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String methodInvocation = "field.getName().getValue"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation field.getName().getValue()")); - assertThat(jpmmlVisitor.useFieldNameGetValue(toTest)).isTrue(); - classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.FieldName;\n" + - "\n" + - "class Stub {\n" + - " public String hello(FieldName field) {\n" + - " System.out.println(field.getValue());\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - methodInvocation = "field.getValue"; - toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation field.getValue()")); - assertThat(jpmmlVisitor.useFieldNameGetValue(toTest)).isTrue(); - } - - @Test - public void useFieldNameGetValue_false() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.DataField;\n" + - "\n" + - "class Stub {\n" + - " public String hello(DataField field) {\n" + - " System.out.println(field.getName().getValue());\n" + - " return \"Hello from com.yourorg.FooBar!\";\n" + - " }\n" + - "}"; - String methodInvocation = "System.out.println"; - J.MethodInvocation toTest = getMethodInvocationFromClassSource(classTested, methodInvocation) - .orElseThrow(() -> new RuntimeException("Failed to find J.MethodInvocation System.out.println()")); - assertThat(jpmmlVisitor.useFieldNameGetValue(toTest)).isFalse(); - } - - @Test - public void toMigrate_False() { - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import java.util.Map;\n" + - "class FooBar {\n" + - "};"; - List toTest = getImportsFromClassSource(classTested); - assertThat(jpmmlVisitor.toMigrate(toTest)) - .isFalse(); - assertThat(toTest).hasSize(2); - } - - @Test - public void toMigrate_True() { - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.FieldName;\n" + - "class FooBar {\n" + - "};"; - List toTest = getImportsFromClassSource(classTested); - assertThat(jpmmlVisitor.toMigrate(toTest)) - .isTrue(); - classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.jpmml.model.inlinetable.InputCell;\n" + - "class FooBar {\n" + - "};"; - toTest = getImportsFromClassSource(classTested); - assertThat(jpmmlVisitor.toMigrate(toTest)) - .isTrue(); - } - - @Test - public void updateMethodToTargetInstantiatedType() { - JavaType.Method toTest = new JavaType.Method(null, 1025, jpmmlVisitor.originalInstantiatedType, "toArray", - jpmmlVisitor.originalInstantiatedType, - Collections.emptyList(), - Collections.emptyList(), null, null); - JavaType.Method retrieved = jpmmlVisitor.updateMethodToTargetInstantiatedType(toTest); - assertThat(retrieved.getDeclaringType()).isEqualTo(jpmmlVisitor.targetInstantiatedType); - assertThat(retrieved.getReturnType()).isEqualTo(jpmmlVisitor.targetInstantiatedType); - } - - @Test - public void updateTypeTreeToTargetInstantiatedType() { - String classTested = "package com.yourorg;\n" + - "\n" + - "import org.dmg.pmml.ScoreDistribution;\n" + - "\n" + - "public class Stub {\n" + - "\n" + - " public void hello() {\n" + - " ScoreDistribution scoreDistribution = new ScoreDistribution();\n" + - " }\n" + - "\n" + - "}"; - String classInstantiated = "org.dmg.pmml.ScoreDistribution"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.ScoreDistribution")); - assertThat(toTest) - .isNotNull(); - TypeTree retrieved = jpmmlVisitor.updateTypeTreeToTargetInstantiatedType(toTest); - assertThat(retrieved) - .isNotNull() - .isInstanceOf(J.Identifier.class); - assertThat(retrieved.getType()).isEqualTo(jpmmlVisitor.targetInstantiatedType); - assertThat(((J.Identifier) retrieved).getSimpleName()).isEqualTo(((JavaType.ShallowClass) jpmmlVisitor.targetInstantiatedType).getClassName()); - } - - @Test - public void removedListaTuple_getJMethod() { - JPMMLVisitor.RemovedListTuple removedListTuple = new JPMMLVisitor.RemovedListTuple("addDataFields", JavaType.buildType("org.dmg.pmml.DataField")); - String classTested = "package com.yourorg;\n" + - "import java.util.List;\n" + - "import org.dmg.pmml.DataDictionary;\n" + - "import org.dmg.pmml.DataField;\n" + - "class FooBar {\n" + - "static void method(List dataFields) {\n" + - "DataDictionary dataDictionary = new DataDictionary(dataFields);\n" + - "}\n" + - "}"; - String classInstantiated = "org.dmg.pmml.DataDictionary"; - J.NewClass toTest = getNewClassFromClassSource(classTested, classInstantiated) - .orElseThrow(() -> new RuntimeException("Failed to find J.NewClass org.dmg.pmml.DataDictionary")); - J.MethodInvocation retrieved = removedListTuple.getJMethod(toTest); - String expected = "new DataDictionary().addDataFields(dataFields.toArray(new org.dmg.pmml.DataField[0]))"; - assertThat(retrieved) - .isNotNull() - .hasToString(expected); - - } -} \ No newline at end of file diff --git a/kie-api/src/main/java/org/kie/api/event/process/ErrorEvent.java b/kie-api/src/main/java/org/kie/api/event/process/ErrorEvent.java new file mode 100644 index 00000000000..af2cc4e0568 --- /dev/null +++ b/kie-api/src/main/java/org/kie/api/event/process/ErrorEvent.java @@ -0,0 +1,33 @@ +/** + * 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.api.event.process; + +import org.kie.api.runtime.process.NodeInstance; + +/** + * An event when a error is thrown + */ +public interface ErrorEvent extends ProcessNodeEvent { + /** + * Error associated to the event + * + * @return exception + */ + Exception getException(); +} diff --git a/kie-api/src/main/java/org/kie/api/event/process/ProcessEventListener.java b/kie-api/src/main/java/org/kie/api/event/process/ProcessEventListener.java index 4f27158b6b1..085f7a34a3b 100644 --- a/kie-api/src/main/java/org/kie/api/event/process/ProcessEventListener.java +++ b/kie-api/src/main/java/org/kie/api/event/process/ProcessEventListener.java @@ -116,4 +116,10 @@ default void onSignal(SignalEvent event) {} * @param event */ default void onMessage(MessageEvent event) {} + + /** + * This listener method is invoked when an error is captured + * @param event + */ + default void onError (ErrorEvent event) {} } diff --git a/kie-dmn/kie-dmn-api/src/main/java/org/kie/dmn/api/core/DMNType.java b/kie-dmn/kie-dmn-api/src/main/java/org/kie/dmn/api/core/DMNType.java index 6bc053ec491..c6357966389 100644 --- a/kie-dmn/kie-dmn-api/src/main/java/org/kie/dmn/api/core/DMNType.java +++ b/kie-dmn/kie-dmn-api/src/main/java/org/kie/dmn/api/core/DMNType.java @@ -100,6 +100,11 @@ * This is reflected in this DMN type {@link #getAllowedValues()}.
*
* It is important to note that attribute can only be present when the type is specified by reference. + *

typeConstraints

+ * As per the DMN specification, the {@link ItemDefinition#getTypeConstraint()} ()} attribute lists the possible values or ranges of values in the base type that are allowed in this ItemDefinition. + * This is reflected in this DMN type {@link #getTypeConstraint()}.
+ *
+ * It is important to note that attribute can only be present when the type is specified by reference. *

getFields

* Only when a type is specified by composition, {@link #getFields()} will return the collection of the fields which constitutes the composite type.
*
@@ -204,7 +209,7 @@ public interface DMNType DMNType clone(); /** - * Definition of `instance of` accordingly to FEEL specifications Table 49. + * Definition of `instance of` accordingly to FEEL specifications Table 61. * @param o * @return if o is instance of the type represented by this type. If the parameter is null, returns false. */ @@ -212,10 +217,16 @@ public interface DMNType /** * Check if the value passed as parameter can be assigned to this type. + * It checks + * 1. type itself + * 2. allowedValues + * 3. typeConstraint * @param value * @return if value can be assigned to the type represented by this type. If the parameter is null, returns true. */ boolean isAssignableValue(Object value); List getAllowedValues(); + + List getTypeConstraint(); } diff --git a/kie-dmn/kie-dmn-core/pom.xml b/kie-dmn/kie-dmn-core/pom.xml index cd5b62edf9d..092bb3931b1 100644 --- a/kie-dmn/kie-dmn-core/pom.xml +++ b/kie-dmn/kie-dmn-core/pom.xml @@ -55,6 +55,13 @@ tests test + + org.kie + kie-dmn-test-resources + ${project.version} + tests + test + @@ -179,6 +186,12 @@ tests test + + org.kie + kie-dmn-test-resources + tests + test + io.smallrye jandex diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNContextEvaluator.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNContextEvaluator.java index 9e710d761f6..4fbb5c0c649 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNContextEvaluator.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNContextEvaluator.java @@ -19,7 +19,6 @@ package org.kie.dmn.core.ast; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -45,6 +44,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.kie.dmn.core.util.CoerceUtil.coerceValue; + public class DMNContextEvaluator implements DMNExpressionEvaluator { private static final Logger logger = LoggerFactory.getLogger( DMNContextEvaluator.class ); @@ -98,14 +99,7 @@ public EvaluatorResult evaluate(DMNRuntimeEventManager eventManager, DMNResult d } EvaluatorResult er = ed.getEvaluator().evaluate( eventManager, result ); if ( er.getResultType() == ResultType.SUCCESS ) { - Object value = er.getResult(); - if( ! ed.getType().isCollection() && value instanceof Collection && - ((Collection)value).size()==1 ) { - // spec defines that "a=[a]", i.e., singleton collections should be treated as the single element - // and vice-versa - value = ((Collection)value).toArray()[0]; - } - + Object value = coerceValue(ed.getType(), er.getResult()); if (((DMNRuntimeImpl) eventManager.getRuntime()).performRuntimeTypeCheck(result.getModel())) { if (!(ed.getContextEntry().getExpression() instanceof FunctionDefinition)) { // checking directly the result type... diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNFunctionDefinitionEvaluator.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNFunctionDefinitionEvaluator.java index ebb45e7e288..c7d6469aaca 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNFunctionDefinitionEvaluator.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/ast/DMNFunctionDefinitionEvaluator.java @@ -48,6 +48,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.kie.dmn.core.util.CoerceUtil.coerceValue; + public class DMNFunctionDefinitionEvaluator implements DMNExpressionEvaluator { private static final Logger logger = LoggerFactory.getLogger( DMNFunctionDefinitionEvaluator.class ); @@ -157,8 +159,9 @@ public Object invoke(EvaluationContext ctx, Object[] params) { closureContext.getAll().forEach(dmnContext::set); for( int i = 0; i < params.length; i++ ) { final String paramName = parameters.get(i).name; - if ((!performRuntimeTypeCheck) || parameters.get(i).type.isAssignableValue(params[i])) { - ctx.setValue(paramName, params[i]); + Object coercedObject = coerceValue(parameters.get(i).type, params[i]); + if ((!performRuntimeTypeCheck) || parameters.get(i).type.isAssignableValue(coercedObject)) { + ctx.setValue(paramName, coercedObject); } else { ctx.setValue(paramName, null); MsgUtil.reportMessage(logger, 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 364b5933f85..640a5c002af 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 @@ -33,7 +33,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -42,7 +41,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.xml.XMLConstants; + import javax.xml.namespace.QName; import org.drools.io.FileSystemResource; @@ -76,6 +75,7 @@ import org.kie.dmn.core.pmml.DMNImportPMMLInfo; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; +import org.kie.dmn.core.util.NamespaceUtil; import org.kie.dmn.feel.lang.FEELProfile; import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.types.AliasFEELType; @@ -106,6 +106,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.processMergedModel; + public class DMNCompilerImpl implements DMNCompiler { private static final Logger logger = LoggerFactory.getLogger( DMNCompilerImpl.class ); @@ -211,7 +213,7 @@ public DMNModel compile(Definitions dmndefs, Collection dmnModels, Res DMNFEELHelper feel = new DMNFEELHelper(cc.getRootClassLoader(), helperFEELProfiles); DMNCompilerContext ctx = new DMNCompilerContext(feel); ctx.setRelativeResolver(relativeResolver); - + List toMerge = new ArrayList<>(); if (!dmndefs.getImport().isEmpty()) { for (Import i : dmndefs.getImport()) { if (ImportDMNResolverUtil.whichImportType(i) == ImportType.DMN) { @@ -230,8 +232,15 @@ public DMNModel compile(Definitions dmndefs, Collection dmnModels, Res }, Function.identity()); if (located != null) { String iAlias = Optional.ofNullable(i.getName()).orElse(located.getName()); - model.setImportAliasForNS(iAlias, located.getNamespace(), located.getName()); - importFromModel(model, located, iAlias); + // incubator-kie-issues#852: The idea is to not treat the anonymous models as import, but to "merge" them + // with original one, + // because otherwise we would have to deal with clashing name aliases, or similar issues + if (iAlias != null && !iAlias.isEmpty()) { + model.setImportAliasForNS(iAlias, located.getNamespace(), located.getName()); + importFromModel(model, located, iAlias); + } else { + toMerge.add(located); + } } } else if (ImportDMNResolverUtil.whichImportType(i) == ImportType.PMML) { processPMMLImport(model, i, relativeResolver); @@ -249,6 +258,7 @@ public DMNModel compile(Definitions dmndefs, Collection dmnModels, Res } } + toMerge.forEach(mergedModel -> processMergedModel(model, (DMNModelImpl) mergedModel)); processItemDefinitions(ctx, model, dmndefs); processDrgElements(ctx, model, dmndefs); return model; @@ -600,10 +610,11 @@ private DMNType buildTypeDef(DMNCompilerContext ctx, DMNModelImpl dmnModel, DMNN type = (BaseDMNTypeImpl) resolveTypeRef(dmnModel, itemDef, itemDef, itemDef.getTypeRef()); if ( type != null ) { UnaryTests allowedValuesStr = itemDef.getAllowedValues(); + UnaryTests typeConstraintStr = itemDef.getTypeConstraint(); // we only want to clone the type definition if it is a top level type (not a field in a composite type) // or if it changes the metadata for the base type - if (topLevel == null || allowedValuesStr != null || itemDef.isIsCollection() != type.isCollection()) { + if (topLevel == null || allowedValuesStr != null || typeConstraintStr != null || itemDef.isIsCollection() != type.isCollection()) { // we have to clone this type definition into a new one String name = itemDef.getName(); @@ -616,18 +627,11 @@ private DMNType buildTypeDef(DMNCompilerContext ctx, DMNModelImpl dmnModel, DMNN baseFEELType = new AliasFEELType(itemDef.getName(), (BuiltInType) baseFEELType); } - List av = null; - if ( allowedValuesStr != null ) { - av = ctx.getFeelHelper().evaluateUnaryTests( - ctx, - allowedValuesStr.getText(), - dmnModel, - itemDef, - Msg.ERR_COMPILING_ALLOWED_VALUES_LIST_ON_ITEM_DEF, - allowedValuesStr.getText(), - node.getName() - ); - } + List allowedValues = getUnaryTests(allowedValuesStr, ctx, dmnModel, node, itemDef, + Msg.ERR_COMPILING_ALLOWED_VALUES_LIST_ON_ITEM_DEF); + List typeConstraint = getUnaryTests(typeConstraintStr, ctx, dmnModel, node, itemDef, + Msg.ERR_COMPILING_TYPE_CONSTRAINT_LIST_ON_ITEM_DEF); + boolean isCollection = itemDef.isIsCollection(); if (isCollection) { @@ -638,7 +642,8 @@ private DMNType buildTypeDef(DMNCompilerContext ctx, DMNModelImpl dmnModel, DMNN CompositeTypeImpl compositeTypeImpl = (CompositeTypeImpl) type; type = new CompositeTypeImpl(namespace, name, id, isCollection, compositeTypeImpl.getFields(), baseType, baseFEELType); } else if (type instanceof SimpleTypeImpl) { - type = new SimpleTypeImpl(namespace, name, id, isCollection, av, baseType, baseFEELType); + type = new SimpleTypeImpl(namespace, name, id, isCollection, allowedValues, typeConstraint, + baseType, baseFEELType); } if (topLevel != null) { type.setBelongingType(topLevel); @@ -702,7 +707,8 @@ private DMNType buildTypeDef(DMNCompilerContext ctx, DMNModelImpl dmnModel, DMNN } } else { BaseDMNTypeImpl unknown = (BaseDMNTypeImpl) resolveTypeRef(dmnModel, itemDef, itemDef, null); - type = new SimpleTypeImpl(dmnModel.getNamespace(), itemDef.getName(), itemDef.getId(), itemDef.isIsCollection(), null, unknown, unknown.getFeelType()); + type = new SimpleTypeImpl(dmnModel.getNamespace(), itemDef.getName(), itemDef.getId(), + itemDef.isIsCollection(), null, null, unknown, unknown.getFeelType()); if (topLevel == null) { DMNType registered = dmnModel.getTypeRegistry().registerType(type); if (registered != type) { @@ -722,6 +728,23 @@ private DMNType buildTypeDef(DMNCompilerContext ctx, DMNModelImpl dmnModel, DMNN return type; } + private List getUnaryTests(UnaryTests unaryTestsToRead, DMNCompilerContext ctx, DMNModelImpl dmnModel, + DMNNode node, ItemDefinition itemDef, Msg.Message2 message) { + List toReturn = null; + if (unaryTestsToRead != null) { + toReturn = ctx.getFeelHelper().evaluateUnaryTests( + ctx, + unaryTestsToRead.getText(), + dmnModel, + itemDef, + message, + unaryTestsToRead.getText(), + node.getName() + ); + } + return toReturn; + } + private static boolean isFunctionItem(ItemDefinition itemDef) { return !(itemDef instanceof org.kie.dmn.model.v1_1.KieDMNModelInstrumentedBase) && !(itemDef instanceof org.kie.dmn.model.v1_2.KieDMNModelInstrumentedBase) && itemDef.getFunctionItem() != null; } @@ -732,7 +755,7 @@ private static boolean isFunctionItem(ItemDefinition itemDef) { */ public DMNType resolveTypeRef(DMNModelImpl dmnModel, NamedElement model, DMNModelInstrumentedBase localElement, QName typeRef) { if ( typeRef != null ) { - QName nsAndName = getNamespaceAndName(localElement, dmnModel.getImportAliasesForNS(), typeRef, dmnModel.getNamespace()); + QName nsAndName = NamespaceUtil.getNamespaceAndName(localElement, dmnModel.getImportAliasesForNS(), typeRef, dmnModel.getNamespace()); DMNType type = dmnModel.getTypeRegistry().resolveType(nsAndName.getNamespaceURI(), nsAndName.getLocalPart()); if (type == null && localElement.getURIFEEL().equals(nsAndName.getNamespaceURI())) { @@ -792,52 +815,6 @@ DMNType resolveTypeRefUsingString(DMNModelImpl dmnModel, NamedElement model, DMN return resolveTypeRef(dmnModel, model, localElement, parseQNameString(typeRef)); } - /** - * Given a typeRef in the form of prefix:localname or importalias.localname, resolves namespace and localname appropriately. - *
Example: feel:string would be resolved as http://www.omg.org/spec/FEEL/20140401, string. - *
Example: myimport.tPerson assuming an external model namespace as "http://drools.org" would be resolved as http://drools.org, tPerson. - * @param localElement the local element is used to determine the namespace from the prefix if present, as in the form prefix:localname - * @param importAliases the map of import aliases is used to determine the namespace, as in the form importalias.localname - * @param typeRef the typeRef to be resolved. - * @return - */ - public static QName getNamespaceAndName(DMNModelInstrumentedBase localElement, Map importAliases, QName typeRef, String modelNamespace) { - if (localElement instanceof org.kie.dmn.model.v1_1.KieDMNModelInstrumentedBase) { - if (!typeRef.getPrefix().equals(XMLConstants.DEFAULT_NS_PREFIX)) { - return new QName(localElement.getNamespaceURI(typeRef.getPrefix()), typeRef.getLocalPart()); - } else { - for (Entry alias : importAliases.entrySet()) { - String prefix = alias.getKey() + "."; - if (typeRef.getLocalPart().startsWith(prefix)) { - return new QName(alias.getValue().getNamespaceURI(), typeRef.getLocalPart().replace(prefix, "")); - } - } - return new QName(localElement.getNamespaceURI(typeRef.getPrefix()), typeRef.getLocalPart()); - } - } else { // DMN v1.2 onwards: - for (BuiltInType bi : DMNTypeRegistryV12.ITEMDEF_TYPEREF_FEEL_BUILTIN) { - for (String biName : bi.getNames()) { - if (biName.equals(typeRef.getLocalPart())) { - return new QName(localElement.getURIFEEL(), typeRef.getLocalPart()); - } - } - } - for (Entry alias : importAliases.entrySet()) { - String prefix = alias.getKey() + "."; - if (typeRef.getLocalPart().startsWith(prefix)) { - return new QName(alias.getValue().getNamespaceURI(), typeRef.getLocalPart().replace(prefix, "")); - } - } - for (String nsKey : localElement.recurseNsKeys()) { - String prefix = nsKey + "."; - if (typeRef.getLocalPart().startsWith(prefix)) { - return new QName(localElement.getNamespaceURI(nsKey), typeRef.getLocalPart().replace(prefix, "")); - } - } - return new QName(modelNamespace, typeRef.getLocalPart()); - } - } - public DMNCompilerConfiguration getDmnCompilerConfig() { return this.dmnCompilerConfig; } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java index ef0940f036d..185a963f298 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNEvaluatorCompiler.java @@ -61,6 +61,7 @@ import org.kie.dmn.core.pmml.PMMLModelInfo; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; +import org.kie.dmn.core.util.NamespaceUtil; import org.kie.dmn.feel.FEEL; import org.kie.dmn.feel.lang.CompiledExpression; import org.kie.dmn.feel.lang.impl.RootExecutionFrame; @@ -601,7 +602,7 @@ protected DMNExpressionEvaluator compileDecisionTable(DMNCompilerContext ctx, DM index ); } else if ( ic.getInputExpression().getTypeRef() != null ) { QName inputExpressionTypeRef = ic.getInputExpression().getTypeRef(); - QName resolvedInputExpressionTypeRef = DMNCompilerImpl.getNamespaceAndName(ic.getInputExpression(), model.getImportAliasesForNS(), inputExpressionTypeRef, model.getNamespace()); + QName resolvedInputExpressionTypeRef = NamespaceUtil.getNamespaceAndName(ic.getInputExpression(), model.getImportAliasesForNS(), inputExpressionTypeRef, model.getNamespace()); BaseDMNTypeImpl typeRef = (BaseDMNTypeImpl) model.getTypeRegistry().resolveType(resolvedInputExpressionTypeRef.getNamespaceURI(), resolvedInputExpressionTypeRef.getLocalPart()); inputType = typeRef; if (inputType == null) { @@ -764,7 +765,7 @@ public static BaseDMNTypeImpl inferTypeRef( DMNModelImpl model, DecisionTable dt BaseDMNTypeImpl typeRef = (BaseDMNTypeImpl) model.getTypeRegistry().unknown(); if ( oc.getTypeRef() != null ) { QName outputExpressionTypeRef = oc.getTypeRef(); - QName resolvedOutputExpressionTypeRef = DMNCompilerImpl.getNamespaceAndName(oc, model.getImportAliasesForNS(), outputExpressionTypeRef, model.getNamespace()); + QName resolvedOutputExpressionTypeRef = NamespaceUtil.getNamespaceAndName(oc, model.getImportAliasesForNS(), outputExpressionTypeRef, model.getNamespace()); typeRef = (BaseDMNTypeImpl) model.getTypeRegistry().resolveType(resolvedOutputExpressionTypeRef.getNamespaceURI(), resolvedOutputExpressionTypeRef.getLocalPart()); if( typeRef == null ) { typeRef = (BaseDMNTypeImpl) model.getTypeRegistry().unknown(); @@ -773,7 +774,7 @@ public static BaseDMNTypeImpl inferTypeRef( DMNModelImpl model, DecisionTable dt QName inferredTypeRef = recurseUpToInferTypeRef(model, oc, dt); // if inferredTypeRef is null, a std err will have been reported if (inferredTypeRef != null) { - QName resolvedInferredTypeRef = DMNCompilerImpl.getNamespaceAndName(oc, model.getImportAliasesForNS(), inferredTypeRef, model.getNamespace()); + QName resolvedInferredTypeRef = NamespaceUtil.getNamespaceAndName(oc, model.getImportAliasesForNS(), inferredTypeRef, model.getNamespace()); typeRef = (BaseDMNTypeImpl) model.getTypeRegistry().resolveType(resolvedInferredTypeRef.getNamespaceURI(), resolvedInferredTypeRef.getLocalPart()); } } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNFEELHelper.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNFEELHelper.java index c66a673a779..c90326bdde8 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNFEELHelper.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNFEELHelper.java @@ -27,6 +27,10 @@ import java.util.Map; import java.util.Queue; +import javax.xml.namespace.QName; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import org.antlr.v4.runtime.CommonToken; import org.kie.dmn.api.core.DMNContext; import org.kie.dmn.api.core.DMNMessage; @@ -37,6 +41,7 @@ import org.kie.dmn.core.impl.DMNModelImpl; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; +import org.kie.dmn.core.util.NamespaceUtil; import org.kie.dmn.feel.FEEL; import org.kie.dmn.feel.codegen.feel11.ProcessedUnaryTest; import org.kie.dmn.feel.lang.CompiledExpression; @@ -53,11 +58,10 @@ import org.kie.dmn.feel.runtime.events.UnknownVariableErrorEvent; import org.kie.dmn.feel.util.ClassLoaderUtil; import org.kie.dmn.model.api.DMNElement; +import org.kie.dmn.model.api.ItemDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; public class DMNFEELHelper { @@ -109,6 +113,10 @@ public static boolean valueMatchesInUnaryTests(List unaryTests, Objec for ( UnaryTest t : unaryTests ) { try { + // allow usage of ? as place-holder inside UnaryTest + if (!ctx.isDefined("?")) { + ctx.setValue("?", value); + } Boolean applyT = t.apply( ctx, value ); // the unary test above can actually return null, so we have to handle it here if ( applyT == null ) { @@ -172,6 +180,24 @@ public List evaluateUnaryTests(DMNCompilerContext ctx, String unaryTe for ( Map.Entry entry : ctx.getVariables().entrySet() ) { variableTypes.put( entry.getKey(), ((BaseDMNTypeImpl) entry.getValue()).getFeelType() ); } + // allow usage of ? as place-holder inside UnaryTest + if (!variableTypes.containsKey("?") && element instanceof ItemDefinition itemDef) { + String nameSpace; + String name; + if (itemDef.isIsCollection()) { + nameSpace = model.getTypeRegistry().feelNS(); + name = "list"; + } else { + QName typeRef = itemDef.getTypeRef(); + QName nsAndName = NamespaceUtil.getNamespaceAndName(element, model.getImportAliasesForNS(), typeRef, + model.getNamespace()); + nameSpace = nsAndName.getNamespaceURI(); + name = nsAndName.getLocalPart(); + } + BaseDMNTypeImpl toSet = (BaseDMNTypeImpl) model.getTypeRegistry().resolveType(nameSpace, name); + variableTypes.put("?", toSet.getFeelType()); + } + result = feel.evaluateUnaryTests( unaryTests, variableTypes ); } catch( Throwable t ) { logger.error( "Error evaluating unary tests. Error will be reported in the model.", t ); diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistry.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistry.java index b422f984897..dc7e378f7c3 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistry.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DMNTypeRegistry.java @@ -18,6 +18,8 @@ */ package org.kie.dmn.core.compiler; +import java.util.Map; + import org.kie.dmn.api.core.DMNType; import org.kie.dmn.feel.lang.types.FEELTypeRegistry; @@ -25,8 +27,12 @@ public interface DMNTypeRegistry extends FEELTypeRegistry { DMNType unknown(); + Map> getTypes(); + DMNType registerType(DMNType type); DMNType resolveType(String namespace, String name); + String feelNS(); + } 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 18eeda7118b..a902e7b0b11 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 @@ -45,7 +45,6 @@ public abstract class DMNTypeRegistryAbstract implements DMNTypeRegistry, FEELTy protected ScopeImpl feelTypesScope = new ScopeImpl(); // no parent scope, intentional. protected Map feelTypesScopeChildLU = new HashMap<>(); - protected abstract String feelNS(); public DMNTypeRegistryAbstract(Map aliases) { this.aliases = aliases; @@ -65,11 +64,11 @@ public DMNTypeRegistryAbstract(Map aliases) { // already added, skip it continue; } else if( type == BuiltInType.LIST ) { - feelPrimitiveType = new SimpleTypeImpl(feelNamespace, name, null, false, null, unknown(), type); + feelPrimitiveType = new SimpleTypeImpl(feelNamespace, name, null, false, null, null, unknown(), type); } else if( type == BuiltInType.CONTEXT ) { feelPrimitiveType = new CompositeTypeImpl( feelNamespace, name, null, false, Collections.emptyMap(), null, type ); } else { - feelPrimitiveType = new SimpleTypeImpl( feelNamespace, name, null, false, null, null, type ); + feelPrimitiveType = new SimpleTypeImpl( feelNamespace, name, null, false, null, null, null, type ); } feelTypes.put( name, feelPrimitiveType ); feelTypesScope.define(new TypeSymbol(name, type)); @@ -93,6 +92,11 @@ public Type resolveFEELType(List qns) { } } + @Override + public Map> getTypes() { + return types; + } + protected void registerAsFEELType(DMNType dmnType) { Optional optAliasKey = keyfromNS(dmnType.getNamespace()); Type feelType = ((BaseDMNTypeImpl) dmnType).getFeelType(); 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 c70b10c67e5..cee46ab7672 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 @@ -34,14 +34,11 @@ public DMNTypeRegistryV11(Map aliases) { } @Override - protected String feelNS() { + public String feelNS() { return KieDMNModelInstrumentedBase.URI_FEEL; } - private static final DMNType UNKNOWN = new SimpleTypeImpl(KieDMNModelInstrumentedBase.URI_FEEL, - BuiltInType.UNKNOWN.getName(), - null, true, null, null, - BuiltInType.UNKNOWN ); + private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); @Override public DMNType unknown() { 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 e3c8dbdc62d..f146d2e1a2d 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 @@ -32,10 +32,7 @@ public class DMNTypeRegistryV12 extends DMNTypeRegistryAbstract { - private static final DMNType UNKNOWN = new SimpleTypeImpl(KieDMNModelInstrumentedBase.URI_FEEL, - BuiltInType.UNKNOWN.getName(), - null, true, null, null, - BuiltInType.UNKNOWN ); + private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); public DMNTypeRegistryV12() { super(Collections.emptyMap()); @@ -67,7 +64,7 @@ public DMNType unknown() { BuiltInType.CONTEXT)); @Override - protected String feelNS() { + public String feelNS() { return KieDMNModelInstrumentedBase.URI_FEEL; } 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 efc927bd0dd..45c0ed87f8f 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,15 +24,11 @@ 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.model.v1_3.KieDMNModelInstrumentedBase; public class DMNTypeRegistryV13 extends DMNTypeRegistryAbstract { - private static final DMNType UNKNOWN = new SimpleTypeImpl(KieDMNModelInstrumentedBase.URI_FEEL, - BuiltInType.UNKNOWN.getName(), - null, true, null, null, - BuiltInType.UNKNOWN ); + private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); public DMNTypeRegistryV13(Map aliases) { super(aliases); @@ -44,7 +40,7 @@ public DMNType unknown() { } @Override - protected String feelNS() { + public String feelNS() { return KieDMNModelInstrumentedBase.URI_FEEL; } } 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 ba7d655b222..a39b5aeb421 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,15 +24,11 @@ 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.model.v1_4.KieDMNModelInstrumentedBase; public class DMNTypeRegistryV14 extends DMNTypeRegistryAbstract { - private static final DMNType UNKNOWN = new SimpleTypeImpl(KieDMNModelInstrumentedBase.URI_FEEL, - BuiltInType.UNKNOWN.getName(), - null, true, null, null, - BuiltInType.UNKNOWN ); + private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); public DMNTypeRegistryV14(Map aliases) { super(aliases); @@ -44,7 +40,7 @@ public DMNType unknown() { } @Override - protected String feelNS() { + public String feelNS() { return KieDMNModelInstrumentedBase.URI_FEEL; } } 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 9c17a476005..332fe992b9d 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,7 +20,6 @@ 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.model.v1_5.KieDMNModelInstrumentedBase; import javax.xml.namespace.QName; @@ -28,10 +27,8 @@ public class DMNTypeRegistryV15 extends DMNTypeRegistryAbstract { - private static final DMNType UNKNOWN = new SimpleTypeImpl(KieDMNModelInstrumentedBase.URI_FEEL, - BuiltInType.UNKNOWN.getName(), - null, true, null, null, - BuiltInType.UNKNOWN ); + private static final DMNType UNKNOWN = SimpleTypeImpl.UNKNOWN_DMNTYPE(KieDMNModelInstrumentedBase.URI_FEEL); + public DMNTypeRegistryV15(Map aliases) { super(aliases); @@ -43,7 +40,7 @@ public DMNType unknown() { } @Override - protected String feelNS() { + public String feelNS() { return KieDMNModelInstrumentedBase.URI_FEEL; } } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionCompiler.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionCompiler.java index 26bfc58b0bd..62c68d50628 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionCompiler.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionCompiler.java @@ -38,6 +38,8 @@ import org.kie.dmn.model.api.DRGElement; import org.kie.dmn.model.api.Decision; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport; + public class DecisionCompiler implements DRGElementCompiler { @Override public boolean accept(DRGElement de) { @@ -96,6 +98,9 @@ public static void loadInCtx(DMNBaseNode node, DMNCompilerContext ctx, DMNModelI if (dep.getModelNamespace().equals(model.getNamespace())) { // for BKMs might need to create a DMNType for "functions" and replace the type here by that ctx.setVariable(dep.getName(), depType); + } else if (isInUnnamedImport(dep, model)) { + // the dependency is an unnamed import + ctx.setVariable(dep.getName(), depType); } else { // then the dependency is an imported dependency. Optional alias = model.getImportAliasFor(dep.getModelNamespace(), dep.getModelName()); @@ -109,4 +114,5 @@ public static void loadInCtx(DMNBaseNode node, DMNCompilerContext ctx, DMNModelI ctx.setVariable(importedType.getKey(), importedType.getValue()); } } + } \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionServiceCompiler.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionServiceCompiler.java index be0e2d9fec1..9e21f0e6997 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionServiceCompiler.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/DecisionServiceCompiler.java @@ -43,6 +43,7 @@ import org.kie.dmn.core.impl.SimpleFnTypeImpl; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; +import org.kie.dmn.core.util.NamespaceUtil; import org.kie.dmn.model.api.DMNElementReference; import org.kie.dmn.model.api.DRGElement; import org.kie.dmn.model.api.DecisionService; @@ -285,7 +286,7 @@ private void checkFnConsistency(DMNModelImpl model, DecisionServiceNodeImpl ni, fdReturnType); } } else if (ni.getDecisionService().getOutputDecision().size() > 1) { - final Function lookupFn = (in) -> DMNCompilerImpl.getNamespaceAndName(ni.getDecisionService(), model.getImportAliasesForNS(), in, model.getNamespace()); + final Function lookupFn = (in) -> NamespaceUtil.getNamespaceAndName(ni.getDecisionService(), model.getImportAliasesForNS(), in, model.getNamespace()); LinkedHashMap fdComposite = new LinkedHashMap<>(); for (DecisionNode dn : outputDecisions) { fdComposite.put(dn.getName(), lookupFn.apply(dn.getDecision().getVariable().getTypeRef())); 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 new file mode 100644 index 00000000000..3f22b334e07 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/compiler/UnnamedImportUtils.java @@ -0,0 +1,76 @@ +/** + * 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.compiler; + +import java.util.Collection; +import java.util.Objects; + +import org.kie.dmn.api.core.ast.DMNNode; +import org.kie.dmn.core.impl.DMNModelImpl; +import org.kie.dmn.model.api.Definitions; +import org.kie.dmn.model.api.Import; +import org.kie.dmn.model.api.NamedElement; + +/** + * Class meant to provide helper methods to deal with unnamed model imports + */ +public class UnnamedImportUtils { + + private UnnamedImportUtils() { + } + + public static boolean isInUnnamedImport(DMNNode node, DMNModelImpl model) { + for (Import imported : model.getDefinitions().getImport()) { + String importedName = imported.getName(); + if ((node.getModelNamespace().equals(imported.getNamespace()) && + (importedName != null && importedName.isEmpty()))) { + return true; + } + } + return false; + } + + public static void processMergedModel(DMNModelImpl parentModel, DMNModelImpl mergedModel) { + // 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 + Definitions parentDefinitions = parentModel.getDefinitions(); + Definitions mergedDefinitions = mergedModel.getDefinitions(); + parentDefinitions.getArtifact().addAll(mergedDefinitions.getArtifact()); + + addIfNotPresent(parentDefinitions.getDecisionService(), mergedDefinitions.getDecisionService()); + addIfNotPresent(parentDefinitions.getBusinessContextElement(), mergedDefinitions.getBusinessContextElement()); + addIfNotPresent(parentDefinitions.getDrgElement(), mergedDefinitions.getDrgElement()); + 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) { + source.forEach(sourceElement -> addIfNotPresent(target, sourceElement)); + } + + static void addIfNotPresent(Collection target, T source) { + if (target.stream().noneMatch(namedElement -> Objects.equals(namedElement.getName(), source.getName()))) { + target.add(source); + } + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/BaseDMNTypeImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/BaseDMNTypeImpl.java index 2484eaef81a..d83039246e1 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/BaseDMNTypeImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/BaseDMNTypeImpl.java @@ -41,6 +41,7 @@ public abstract class BaseDMNTypeImpl private String id; private boolean collection; private List allowedValues; + private List typeConstraint; private DMNType baseType; private Type feelType; private DMNType belongingType; @@ -98,7 +99,12 @@ public boolean isComposite() { @Override public List getAllowedValues() { - return allowedValues != null ? Collections.unmodifiableList((List) allowedValues) : Collections.emptyList(); + return allowedValues != null ? Collections.unmodifiableList(allowedValues) : Collections.emptyList(); + } + + @Override + public List getTypeConstraint() { + return typeConstraint != null ? Collections.unmodifiableList(typeConstraint) : Collections.emptyList(); } public List getAllowedValuesFEEL() { @@ -109,6 +115,15 @@ public void setAllowedValues(List allowedValues) { this.allowedValues = allowedValues; } + public List getTypeConstraintFEEL() { + return typeConstraint; + } + + public void setTypeConstraint(List typeConstraints) { + this.typeConstraint = typeConstraints; + } + + @Override public DMNType getBaseType() { return baseType; @@ -134,58 +149,104 @@ public String toString() { } @Override - public boolean isInstanceOf(Object o) { - if ( o == null ) { + public boolean isInstanceOf(Object value) { + if ( value == null ) { return false; // See FEEL specifications Table 49. } + Object toCheck = getObjectToCheck(value); // try first to recurse in case of Collection.. - if ( isCollection() && o instanceof Collection ) { - Collection elements = (Collection) o; + if ( isCollection() && toCheck instanceof Collection elements) { for ( Object e : elements ) { - if ( !internalIsInstanceOf(e) || !valueMatchesInUnaryTests(e) ) { + // Do not dig inside collection for typeConstraint check + if ( !internalAllowedValueIsInstanceOf(e) || !valueMatchesInUnaryTests(allowedValues, e) ) { return false; } } return true; } - // .. normal case, or collection of 1 element: singleton list + // .. normal case + boolean instanceOfAllowedValue = internalAllowedValueIsInstanceOf(toCheck); + // Also check typeConstraint for not-collection values + boolean instanceOfTypeConstraint = internalTypeConstraintIsInstanceOf(toCheck); + // Also check typeConstraint for not-collection values + return (instanceOfAllowedValue && valueMatchesInUnaryTests(allowedValues, toCheck)) + && (instanceOfTypeConstraint && valueMatchesInUnaryTests(typeConstraint, toCheck)); + } + + private Object getObjectToCheck(Object value) { // spec defines that "a=[a]", i.e., singleton collections should be treated as the single element // and vice-versa - return internalIsInstanceOf(o) && valueMatchesInUnaryTests(o); + // For isCollection type, a single element can be converted to singleton collection + if (isCollection() && !(value instanceof Collection)) { + return Collections.singletonList(value); + } + if (!isCollection() && (value instanceof Collection collection) && collection.size() == 1) { + return collection.iterator().next(); + } + return value; } - - private boolean valueMatchesInUnaryTests(Object o) { - if ( allowedValues == null || allowedValues.isEmpty() ) { + + private boolean valueMatchesInUnaryTests(List unaryTests, Object o) { + if ( unaryTests == null || unaryTests.isEmpty() ) { return true; } else { - return DMNFEELHelper.valueMatchesInUnaryTests(allowedValues, EvalHelper.coerceNumber(o), null); + return DMNFEELHelper.valueMatchesInUnaryTests(unaryTests, EvalHelper.coerceNumber(o), null); } } protected abstract boolean internalIsInstanceOf(Object o); + + protected boolean internalAllowedValueIsInstanceOf(Object o) { + return internalIsInstanceOf(o); + } + + protected boolean internalTypeConstraintIsInstanceOf(Object o) { + return internalIsInstanceOf(o); + } + @Override public boolean isAssignableValue(Object value) { - if (value == null && allowedValues == null) { + if (value == null && allowedValues == null && typeConstraint == null) { return true; // a null-value can be assigned to any type. - } + } + Object toCheck = getObjectToCheck(value); // try first to recurse in case of Collection.. - if ( isCollection() && value instanceof Collection ) { - Collection elements = (Collection) value; + if ( isCollection() && toCheck instanceof Collection elements) { for ( Object e : elements ) { - if ( !internalIsAssignableValue(e) || !valueMatchesInUnaryTests(e) ) { + // Do not dig inside collection for typeContraint check + if ( !internalAllowedValueIsAssignableValue(e) || !valueMatchesInUnaryTests(allowedValues, e) ) { return false; } } - return true; + // If it is a collection, we have to check the typeConstraint on the whole object + return internalTypeConstraintIsAssignableValue(toCheck) && valueMatchesInUnaryTests(typeConstraint, toCheck); } - // .. normal case, or collection of 1 element: singleton list - // spec defines that "a=[a]", i.e., singleton collections should be treated as the single element - // and vice-versa - return internalIsAssignableValue( value ) && valueMatchesInUnaryTests(value); + // .. normal case + boolean assignableAllowedValue = internalAllowedValueIsAssignableValue(toCheck); + // Also check typeConstraint for not-collection values + boolean assignableTypeConstraint = internalTypeConstraintIsAssignableValue(toCheck); + + return (assignableAllowedValue && valueMatchesInUnaryTests(allowedValues, toCheck)) && + (assignableTypeConstraint && valueMatchesInUnaryTests(typeConstraint, toCheck)); } - - protected abstract boolean internalIsAssignableValue(Object o); + + /** + * This method relies mostly on baseType + * Different implementations may provide/extend the logic + * @param o + * @return + */ + protected abstract boolean internalAllowedValueIsAssignableValue(Object o); + + /** + * This method relies mostly on feelType + * Different implementations may provide/extend the logic, mostly depending on isCollection and, eventually, if a MapBackedType is provided + * @param o + * @return + */ + protected abstract boolean internalTypeConstraintIsAssignableValue(Object o); + public void setBelongingType(DMNType belongingType) { this.belongingType = belongingType; diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/CompositeTypeImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/CompositeTypeImpl.java index 491e7676dc3..3f82ad85b47 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/CompositeTypeImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/CompositeTypeImpl.java @@ -20,6 +20,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -92,7 +93,7 @@ public boolean isComposite() { public CompositeTypeImpl clone() { return new CompositeTypeImpl( getNamespace(), getName(), getId(), isCollection(), new LinkedHashMap<>( fields), getBaseType(), getFeelType() ); } - + @Override protected boolean internalIsInstanceOf(Object o) { if (getBaseType() != null) { @@ -132,35 +133,63 @@ protected boolean internalIsInstanceOf(Object o) { } @Override - protected boolean internalIsAssignableValue(Object o) { + protected boolean internalAllowedValueIsAssignableValue(Object o) { if (getBaseType() != null) { return getBaseType().isAssignableValue(o); - } else if (o instanceof Map) { - Map instance = (Map) o; - for ( Entry f : fields.entrySet() ) { - if ( !instance.containsKey(f.getKey()) ) { + } else { + return internalCheckObject(o); + } + } + + @Override + protected boolean internalTypeConstraintIsAssignableValue(Object o) { + return checkByCollection(o) || checkByElement(o); + } + + private boolean internalCheckObject(Object o) { + return o == null || checkByMap(o) || checkByFields(o); + } + + private boolean checkByCollection(Object o) { + // the check of contained elements is done inside BaseDMNType.isAssignableValue + // Here we have only to confirm a) current feel type is a list and b) given object is a collection + return isCollection() && getFeelType() instanceof GenListType && o instanceof Collection; + } + + private boolean checkByElement(Object o) { + return getFeelType() instanceof MapBackedType ? checkByFields(o) : getFeelType().isAssignableValue(o); + } + + private boolean checkByMap(Object o) { + if (o instanceof Map instance) { + for (Entry f : fields.entrySet()) { + if (!instance.containsKey(f.getKey())) { return false; // It must have key named 'f.getKey()' like a Duck. } else { - if ( !f.getValue().isAssignableValue(instance.get(f.getKey())) ) { + if (!f.getValue().isAssignableValue(instance.get(f.getKey()))) { return false; } } } return true; - } else if (o == null) { - return true; // a null-value can be assigned to any type. } else { - for ( Entry f : fields.entrySet() ) { - PropertyValueResult fValue = EvalHelper.getDefinedValue(o, f.getKey()); - if (fValue.isDefined()) { - if (!f.getValue().isAssignableValue(fValue.getValueResult().getOrElseThrow(e -> new IllegalStateException(e)))) { - return false; - } - } else { - return false; // It must like a Duck. + return false; + } + } + + private boolean checkByFields(Object o) { + for (Entry f : fields.entrySet()) { + PropertyValueResult fValue = EvalHelper.getDefinedValue(o, f.getKey()); + if (fValue.isDefined()) { + Object valueResult = fValue.getValueResult().getOrElseThrow(IllegalStateException::new); + DMNType expectedType = f.getValue(); + if (!expectedType.isAssignableValue(valueResult)) { + return false; } + } else { + return false; // It must like a Duck. } - return true; } + return true; } } 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 fafcb18b5df..f7a45a44d36 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 @@ -70,6 +70,8 @@ import org.kie.dmn.model.api.DMNModelInstrumentedBase; import org.kie.dmn.model.api.Definitions; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport; + public class DMNModelImpl implements DMNModel, DMNMessageManager, Externalizable { @@ -205,13 +207,20 @@ public void addDecision(DecisionNode dn) { } private String computeDRGElementModelLocalId(DMNNode node) { + // incubator-kie-issues#852: The idea is to not treat the anonymous models as import, but to "merge" them with original opne, + // Here, if the node comes from an unnamed imported model, then it is stored only with its id, to be looked for + // as if defined in the model itself if (node.getModelNamespace().equals(definitions.getNamespace())) { return node.getId(); + } else if (isInUnnamedImport(node, this)) { + // the node is an unnamed import + return node.getId(); } else { return node.getModelNamespace() + "#" + node.getId(); } } + @Override public DecisionNode getDecisionById(String id) { return this.decisions.get(id); diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNRuntimeImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNRuntimeImpl.java index a0dc822fc77..85ccf42b784 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNRuntimeImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/DMNRuntimeImpl.java @@ -65,6 +65,8 @@ import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.EVALUATING; import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.FAILED; import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.SKIPPED; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport; +import static org.kie.dmn.core.util.CoerceUtil.coerceValue; public class DMNRuntimeImpl implements DMNRuntime { @@ -500,6 +502,9 @@ private boolean walkIntoImportScope(DMNResultImpl result, DMNNode callerNode, DM if (result.getContext().scopeNamespace().isEmpty()) { if (destinationNode.getModelNamespace().equals(result.getModel().getNamespace())) { return false; + } else if (isInUnnamedImport(destinationNode, (DMNModelImpl) result.getModel())) { + // the destinationNode is an unnamed import + return false; } else { Optional importAlias = callerNode.getModelImportAliasFor(destinationNode.getModelNamespace(), destinationNode.getModelName()); if (importAlias.isPresent()) { @@ -661,16 +666,9 @@ private boolean evaluateDecision(DMNContext context, DMNResultImpl result, Decis return false; } try { - EvaluatorResult er = decision.getEvaluator().evaluate( this, result ); + EvaluatorResult er = decision.getEvaluator().evaluate( this, result); if( er.getResultType() == EvaluatorResult.ResultType.SUCCESS ) { - Object value = er.getResult(); - if( ! decision.getResultType().isCollection() && value instanceof Collection && - ((Collection)value).size()==1 ) { - // spec defines that "a=[a]", i.e., singleton collections should be treated as the single element - // and vice-versa - value = ((Collection)value).toArray()[0]; - } - + Object value = coerceValue(decision.getResultType(), er.getResult()); try { if (typeCheck && !d.getResultType().isAssignableValue(value)) { DMNMessage message = MsgUtil.reportMessage( logger, diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleFnTypeImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleFnTypeImpl.java index 203210547c8..a6aea269331 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleFnTypeImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleFnTypeImpl.java @@ -35,7 +35,7 @@ public class SimpleFnTypeImpl extends SimpleTypeImpl { private final FunctionItem fi; public SimpleFnTypeImpl(String namespace, String name, String id, Type feelType, Map params, DMNType returnType, FunctionItem fi) { - super(namespace, name, id, false, null, null, feelType); + super(namespace, name, id, false, null, null, null, feelType); this.params = new HashMap<>(params); this.returnType = returnType; this.fi = fi; diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleTypeImpl.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleTypeImpl.java index 42a2473550a..8311a49f35d 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleTypeImpl.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/impl/SimpleTypeImpl.java @@ -18,10 +18,12 @@ */ package org.kie.dmn.core.impl; +import java.util.Collection; import java.util.List; import org.kie.dmn.api.core.DMNType; import org.kie.dmn.feel.lang.Type; +import org.kie.dmn.feel.lang.types.BuiltInType; import org.kie.dmn.feel.runtime.UnaryTest; /** @@ -30,17 +32,26 @@ public class SimpleTypeImpl extends BaseDMNTypeImpl { + + public static SimpleTypeImpl UNKNOWN_DMNTYPE(String uriFEEL) { + return new SimpleTypeImpl(uriFEEL, + BuiltInType.UNKNOWN.getName(), + null, true, null, null, null, + BuiltInType.UNKNOWN ); + } + public SimpleTypeImpl() { - this( null, null, null, false, null, null, null ); + this( null, null, null, false, null, null,null, null ); } public SimpleTypeImpl(String namespace, String name, String id) { - this( namespace, name, id, false, null, null, null ); + this( namespace, name, id, false, null, null, null, null ); } - public SimpleTypeImpl(String namespace, String name, String id, boolean isCollection, List allowedValues, DMNType baseType, Type feelType) { + public SimpleTypeImpl(String namespace, String name, String id, boolean isCollection, List allowedValues, List typeConstraint, DMNType baseType, Type feelType) { super(namespace, name, id, isCollection, baseType, feelType); setAllowedValues( allowedValues ); + setTypeConstraint(typeConstraint); } @Override @@ -49,7 +60,7 @@ public boolean isComposite() { } public BaseDMNTypeImpl clone() { - return new SimpleTypeImpl( getNamespace(), getName(), getId(), isCollection(), getAllowedValuesFEEL(), getBaseType(), getFeelType() ); + return new SimpleTypeImpl( getNamespace(), getName(), getId(), isCollection(), getAllowedValuesFEEL(), getTypeConstraintFEEL(), getBaseType(), getFeelType() ); } @Override @@ -58,7 +69,22 @@ protected boolean internalIsInstanceOf(Object o) { } @Override - protected boolean internalIsAssignableValue(Object o) { - return getBaseType() != null ? getBaseType().isAssignableValue(o) : getFeelType().isAssignableValue (o); + protected boolean internalAllowedValueIsInstanceOf(Object o) { + return getBaseType() != null ? getBaseType().isInstanceOf(o) : getFeelType().isInstanceOf(o); + } + + @Override + protected boolean internalTypeConstraintIsInstanceOf(Object o) { + return getFeelType().isInstanceOf(o); + } + + @Override + protected boolean internalAllowedValueIsAssignableValue(Object o) { + return getBaseType() != null ? getBaseType().isAssignableValue(o) : getFeelType().isAssignableValue(o); + } + + @Override + protected boolean internalTypeConstraintIsAssignableValue(Object o) { + return isCollection() ? o instanceof Collection : getFeelType().isAssignableValue(o); } } diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/pmml/DMNImportPMMLInfo.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/pmml/DMNImportPMMLInfo.java index bf47f624c0c..bc0946ab5d3 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/pmml/DMNImportPMMLInfo.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/pmml/DMNImportPMMLInfo.java @@ -94,7 +94,7 @@ public static Either from(InputStream is, DMNCompi av.addAll(ut); } } - DMNType type = new SimpleTypeImpl(i.getNamespace(), dfName, null, false, av, model.getTypeRegistry().resolveType(model.getDefinitions().getURIFEEL(), ft.getName()), ft); + DMNType type = new SimpleTypeImpl(i.getNamespace(), dfName, null, false, av, null, model.getTypeRegistry().resolveType(model.getDefinitions().getURIFEEL(), ft.getName()), ft); model.getTypeRegistry().registerType(type); } @@ -122,12 +122,11 @@ private static void registerOutputFieldType(Model pmmlModel, DMNModelImpl dmnMod outputFields.stream().forEach(field -> { String fieldName =field.getName(); BuiltInType ft = getBuiltInTypeByDataType(field.getDataType()); - DMNType type = new SimpleTypeImpl(i.getNamespace(), fieldName, null, false, null, dmnModel.getTypeRegistry().resolveType(dmnModel.getDefinitions().getURIFEEL(), ft.getName()), ft); + DMNType type = new SimpleTypeImpl(i.getNamespace(), fieldName, null, false, null, null, dmnModel.getTypeRegistry().resolveType(dmnModel.getDefinitions().getURIFEEL(), ft.getName()), ft); typeMap.put(fieldName, type); }); DMNType compositeType = new CompositeTypeImpl(i.getNamespace(), modelName, null, false, typeMap, null, null); dmnModel.getTypeRegistry().registerType(compositeType); - return; } else { // Case of multiple/complex output AND model without name, raise a Warning from the compilation/engine side (for the editor to use FEEL Any as the typeRef in the BKM) LOG.warn("PMML modelName is not provided, while output is a composite / multiple fields. Unable to synthesize CompositeType for DMN side."); diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/CoerceUtil.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/CoerceUtil.java new file mode 100644 index 00000000000..0d59e4563fc --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/CoerceUtil.java @@ -0,0 +1,58 @@ +/** + * 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.util; + +import java.time.LocalDate; +import java.util.Collection; + +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.feel.util.EvalHelper; + +/** + * Class used to centralize all coercion-related behavior + */ +public class CoerceUtil { + + private CoerceUtil() { + // singleton class + } + + public static Object coerceValue(DMNType requiredType, Object valueToCoerce) { + return (requiredType != null && valueToCoerce != null) ? actualCoerceValue(requiredType, valueToCoerce) : + valueToCoerce; + } + + static Object actualCoerceValue(DMNType requiredType, Object valueToCoerce) { + Object toReturn = valueToCoerce; + if (!requiredType.isCollection() && valueToCoerce instanceof Collection && + ((Collection) valueToCoerce).size() == 1) { + // spec defines that "a=[a]", i.e., singleton collections should be treated as the single element + // and vice-versa + return ((Collection) valueToCoerce).toArray()[0]; + } + if (valueToCoerce instanceof LocalDate localDate && + requiredType instanceof SimpleTypeImpl simpleType && + simpleType.getFeelType() == BuiltInType.DATE_TIME) { + return EvalHelper.coerceDateTime(localDate); + } + return toReturn; + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/Msg.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/Msg.java index 0ef042ee872..310b936f57d 100644 --- a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/Msg.java +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/Msg.java @@ -110,6 +110,7 @@ public final class Msg { public static final Message2 ERR_COMPILING_FEEL_EXPR_ON_DT_PARAM = new Message2( DMNMessageType.ERR_COMPILING_FEEL, "Error compiling FEEL expression '%s' parameter of invocation of decision table '%s'"); public static final Message2 ERR_COMPILING_FEEL_EXPR_ON_DT = new Message2( DMNMessageType.ERR_COMPILING_FEEL, "Error compiling FEEL expression '%s' on decision table '%s'" ); public static final Message2 ERR_COMPILING_ALLOWED_VALUES_LIST_ON_ITEM_DEF = new Message2( DMNMessageType.ERR_COMPILING_FEEL, "Error compiling allowed values list '%s' on item definition '%s'" ); + public static final Message2 ERR_COMPILING_TYPE_CONSTRAINT_LIST_ON_ITEM_DEF = new Message2( DMNMessageType.ERR_COMPILING_FEEL, "Error compiling type constraint list '%s' on item definition '%s'" ); public static final Message4 ERR_COMPILING_FEEL_EXPR_FOR_NAME_ON_NODE = new Message4( DMNMessageType.ERR_COMPILING_FEEL, "Error compiling FEEL expression '%s' for name '%s' on node '%s': %s" ); public static final Message2 ERR_EVAL_CTX_ENTRY_ON_CTX = new Message2( DMNMessageType.ERR_EVAL_CTX, "Error evaluating context extry '%s' on context '%s'" ); public static final Message3 ERR_EVAL_CTX_ENTRY_ON_CTX_MSG = new Message3( DMNMessageType.ERR_EVAL_CTX, "Unrecoverable error evaluating context extry '%s' on context '%s': %s" ); diff --git a/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/NamespaceUtil.java b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/NamespaceUtil.java new file mode 100644 index 00000000000..c26f2339b22 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/main/java/org/kie/dmn/core/util/NamespaceUtil.java @@ -0,0 +1,80 @@ +/** + * 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.util; + +import java.util.Map; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; + +import org.kie.dmn.core.compiler.DMNTypeRegistryV12; +import org.kie.dmn.feel.lang.types.BuiltInType; +import org.kie.dmn.model.api.DMNModelInstrumentedBase; + +public class NamespaceUtil { + + private NamespaceUtil() { + } + + /** + * Given a typeRef in the form of prefix:localname or importalias.localname, resolves namespace and localname appropriately. + *
Example: feel:string would be resolved as http://www.omg.org/spec/FEEL/20140401, string. + *
Example: myimport.tPerson assuming an external model namespace as "http://drools.org" would be resolved as http://drools.org, tPerson. + * @param localElement the local element is used to determine the namespace from the prefix if present, as in the form prefix:localname + * @param importAliases the map of import aliases is used to determine the namespace, as in the form importalias.localname + * @param typeRef the typeRef to be resolved. + * @return + */ + public static QName getNamespaceAndName(DMNModelInstrumentedBase localElement, Map importAliases, QName typeRef, String modelNamespace) { + if (localElement instanceof org.kie.dmn.model.v1_1.KieDMNModelInstrumentedBase) { + if (!typeRef.getPrefix().equals(XMLConstants.DEFAULT_NS_PREFIX)) { + return new QName(localElement.getNamespaceURI(typeRef.getPrefix()), typeRef.getLocalPart()); + } else { + for (Map.Entry alias : importAliases.entrySet()) { + String prefix = alias.getKey() + "."; + if (typeRef.getLocalPart().startsWith(prefix)) { + return new QName(alias.getValue().getNamespaceURI(), typeRef.getLocalPart().replace(prefix, "")); + } + } + return new QName(localElement.getNamespaceURI(typeRef.getPrefix()), typeRef.getLocalPart()); + } + } else { // DMN v1.2 onwards: + for (BuiltInType bi : DMNTypeRegistryV12.ITEMDEF_TYPEREF_FEEL_BUILTIN) { + for (String biName : bi.getNames()) { + if (biName.equals(typeRef.getLocalPart())) { + return new QName(localElement.getURIFEEL(), typeRef.getLocalPart()); + } + } + } + for (Map.Entry alias : importAliases.entrySet()) { + String prefix = alias.getKey() + "."; + if (typeRef.getLocalPart().startsWith(prefix)) { + return new QName(alias.getValue().getNamespaceURI(), typeRef.getLocalPart().replace(prefix, "")); + } + } + for (String nsKey : localElement.recurseNsKeys()) { + String prefix = nsKey + "."; + if (typeRef.getLocalPart().startsWith(prefix)) { + return new QName(localElement.getNamespaceURI(nsKey), typeRef.getLocalPart().replace(prefix, "")); + } + } + return new QName(modelNamespace, typeRef.getLocalPart()); + } + } +} \ 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 71e63c9d837..4f4773bfc1f 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 @@ -272,6 +272,98 @@ public void testImport() { } } + @Test + public void testEmptyNamedModelImport() { + final DMNRuntime runtime = 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", + "Imported Model"); + assertThat(importedModel).isNotNull(); + for (final DMNMessage message : importedModel.getMessages()) { + LOG.debug("{}", message); + } + + final DMNModel importingModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", + "Importing empty-named Model"); + assertThat(importingModel).isNotNull(); + for (final DMNMessage message : importingModel.getMessages()) { + LOG.debug("{}", message); + } + + final DMNContext context = runtime.newContext(); + context.set("A Person", mapOf(entry("name", "John"), entry("age", 47))); + + final DMNResult evaluateAll = evaluateModel(runtime, importingModel, context); + for (final DMNMessage message : evaluateAll.getMessages()) { + LOG.debug("{}", message); + } + LOG.debug("{}", evaluateAll); + // Verify locally-defined BusinessKnowledgeModel + assertThat(evaluateAll.getDecisionResultByName("Local Greeting").getResult()).isEqualTo("Local Hello John!"); + + if (isTypeSafe()) { + FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa(); + Map allProperties = outputSet.allFEELProperties(); + assertThat(allProperties).containsEntry("Local Greeting", "Local Hello John!"); + } + // Verify unnamed-imported BusinessKnowledgeModel + assertThat(evaluateAll.getDecisionResultByName("Imported Greeting").getResult()).isEqualTo("Hello John!"); + + if (isTypeSafe()) { + FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa(); + Map allProperties = outputSet.allFEELProperties(); + assertThat(allProperties).containsEntry("Imported Greeting", "Hello John!"); + } + } + + @Test + public void testOverridingEmptyNamedModelImport() { + final DMNRuntime runtime = createRuntimeWithAdditionalResources("Importing_OverridingEmptyNamed_Model.dmn", + this.getClass(), + "Imported_Model_Unamed.dmn"); + + final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + "Imported Model"); + assertThat(importedModel).isNotNull(); + for (final DMNMessage message : importedModel.getMessages()) { + LOG.debug("{}", message); + } + + final DMNModel importingModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", + "Importing empty-named Model"); + assertThat(importingModel).isNotNull(); + for (final DMNMessage message : importingModel.getMessages()) { + LOG.debug("{}", message); + } + + final DMNContext context = runtime.newContext(); + context.set("A Person", mapOf(entry("name", "John"), entry("age", 47))); + + final DMNResult evaluateAll = evaluateModel(runtime, importingModel, context); + for (final DMNMessage message : evaluateAll.getMessages()) { + LOG.debug("{}", message); + } + LOG.debug("{}", evaluateAll); + // Verify locally-defined BusinessKnowledgeModel + assertThat(evaluateAll.getDecisionResultByName("Local Greeting").getResult()).isEqualTo("Local Hello John!"); + + if (isTypeSafe()) { + FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa(); + Map allProperties = outputSet.allFEELProperties(); + assertThat(allProperties).containsEntry("Local Greeting", "Local Hello John!"); + } + // Verify unnamed-imported BusinessKnowledgeModel + assertThat(evaluateAll.getDecisionResultByName("Imported Greeting").getResult()).isEqualTo("Hello John!"); + + if (isTypeSafe()) { + FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa(); + Map allProperties = outputSet.allFEELProperties(); + assertThat(allProperties).containsEntry("Imported Greeting", "Hello John!"); + } + } + @Test public void testWrongComparisonOps() { final DMNRuntime runtime = createRuntime("WrongComparisonOps.dmn", this.getClass()); diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNInputRuntimeTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNInputRuntimeTest.java index b3c8d02b027..d264e6b8ee6 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNInputRuntimeTest.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNInputRuntimeTest.java @@ -256,7 +256,7 @@ public void testNonexistantInputNodeName() { } @Test - public void testAllowedValuesChecks() { + public void testAllowedValuesChecksInsideCollectionElement() { final DMNRuntime runtime = DMNRuntimeUtil.createRuntime( "AllowedValuesChecks.dmn", this.getClass() ); final DMNModel dmnModel = runtime.getModel( "http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442", @@ -289,6 +289,68 @@ public void testAllowedValuesChecks() { assertThat(dmnResult4.getMessages().stream().anyMatch(m -> m.getMessageType().equals(DMNMessageType.ERROR_EVAL_NODE))).isTrue(); } + @Test + public void testAllowedValuesChecksInsideCollection() { + final DMNRuntime runtime = DMNRuntimeUtil.createRuntime( "AllowedValuesChecksInsideCollection.dmn", this.getClass() ); + final DMNModel dmnModel = runtime.getModel( + "http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442", + "AllowedValuesChecksInsideCollection" ); + assertThat(dmnModel).isNotNull(); + assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse(); + + final DMNContext ctx1 = runtime.newContext(); + ctx1.set("p1", prototype(entry("Name", "P1"), entry("Interests", Collections.singletonList("Golf")))); + final DMNResult dmnResult1 = runtime.evaluateAll( dmnModel, ctx1 ); + assertThat(dmnResult1.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult1.getMessages())).isFalse(); + assertThat( dmnResult1.getContext().get( "MyDecision" )).isEqualTo("The Person P1 likes 1 thing(s)." ); + + final DMNContext ctx2 = runtime.newContext(); + ctx2.set("p1", prototype(entry("Name", "P2"), entry("Interests", Collections.singletonList("x")))); + final DMNResult dmnResult2 = runtime.evaluateAll( dmnModel, ctx2 ); + assertThat(dmnResult2.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult2.getMessages())).isTrue(); + assertThat(dmnResult2.getMessages().stream().anyMatch(m -> m.getMessageType().equals(DMNMessageType.ERROR_EVAL_NODE))).isTrue(); + + final DMNContext ctx3 = runtime.newContext(); + ctx3.set("p1", prototype(entry("Name", "P3"), entry("Interests", Arrays.asList("Golf", "Computer")))); + final DMNResult dmnResult3 = runtime.evaluateAll( dmnModel, ctx3 ); + assertThat(dmnResult3.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult3.getMessages())).isFalse(); + assertThat( dmnResult3.getContext().get( "MyDecision" )).isEqualTo("The Person P3 likes 2 thing(s)." ); + + final DMNContext ctx4 = runtime.newContext(); + ctx4.set("p1", prototype(entry("Name", "P4"), entry("Interests", Arrays.asList("Golf", "x")))); + final DMNResult dmnResult4 = runtime.evaluateAll( dmnModel, ctx4 ); + assertThat(dmnResult4.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult4.getMessages())).isTrue(); + assertThat(dmnResult4.getMessages().stream().anyMatch(m -> m.getMessageType().equals(DMNMessageType.ERROR_EVAL_NODE))).isTrue(); + } + + @Test + public void testTypeConstraintsChecks() { + final DMNRuntime runtime = DMNRuntimeUtil.createRuntime( "TypeConstraintsChecks.dmn", this.getClass() ); + final DMNModel dmnModel = runtime.getModel( + "http://www.trisotech.com/definitions/_238bd96d-47cd-4746-831b-504f3e77b442", + "TypeConstraintsChecks" ); + assertThat(dmnModel).isNotNull(); + assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse(); + + final DMNContext ctx1 = runtime.newContext(); + ctx1.set("p1", prototype(entry("Name", "P1"), entry("Interests", Collections.singletonList("Golf")))); + final DMNResult dmnResult1 = runtime.evaluateAll( dmnModel, ctx1 ); + assertThat(dmnResult1.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult1.getMessages())).isFalse(); + assertThat( dmnResult1.getContext().get( "MyDecision" )).isEqualTo("The Person P1 likes 1 thing(s)." ); + + final DMNContext ctx2 = runtime.newContext(); + ctx2.set("p1", prototype(entry("Name", "P2"), entry("Interests", Collections.singletonList("x")))); + final DMNResult dmnResult2 = runtime.evaluateAll( dmnModel, ctx2 ); + assertThat(dmnResult2.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult2.getMessages())).isFalse(); + assertThat( dmnResult2.getContext().get( "MyDecision" )).isEqualTo("The Person P2 likes 1 thing(s)." ); + + final DMNContext ctx3 = runtime.newContext(); + ctx3.set("p1", prototype(entry("Name", "P3"), entry("Interests", Arrays.asList("Golf", "Computer")))); + final DMNResult dmnResult3 = runtime.evaluateAll( dmnModel, ctx3 ); + assertThat(dmnResult3.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult3.getMessages())).isTrue(); + assertThat(dmnResult3.getMessages().stream().anyMatch(m -> m.getMessageType().equals(DMNMessageType.ERROR_EVAL_NODE))).isTrue(); + } + @Test public void testDMNInputDataNodeTypeTest() { // DROOLS-1569 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 d8ea2622212..a5198f90a2d 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 @@ -25,6 +25,7 @@ import java.time.LocalTime; import java.time.OffsetTime; import java.time.Period; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.chrono.ChronoPeriod; @@ -634,6 +635,7 @@ public void testDateAndTime() { assertThat(ctx.get("Date-Time")).isEqualTo( ZonedDateTime.of( 2016, 12, 24, 23, 59, 0, 0, ZoneOffset.ofHours( -5 ))); assertThat(ctx.get("Date")).isEqualTo( new HashMap( ) {{ put( "fromString", LocalDate.of( 2015, 12, 24 ) ); + put( "fromStringToDateTime", ZonedDateTime.of( 2015, 12, 24, 0, 0, 0, 0, ZoneOffset.UTC) ); put( "fromDateTime", LocalDate.of( 2016, 12, 24 ) ); put( "fromYearMonthDay", LocalDate.of( 1999, 11, 22 ) ); }}); @@ -658,6 +660,24 @@ public void testDateAndTime() { } + @Test + public void testDateToDateTimeFunction() { + final DMNRuntime runtime = DMNRuntimeUtil.createRuntime("DateToDateTimeFunction.dmn", this.getClass()); + final DMNModel dmnModel = runtime.getModel("https://kiegroup.org/dmn/_A7F17D7B-F0AB-4C0B-B521-02EA26C2FBEE", + "new-file"); + assertThat(dmnModel).isNotNull(); + assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse(); + + final DMNContext ctx = runtime.newContext(); + + final DMNResult dmnResult = runtime.evaluateAll(dmnModel, ctx); + LOG.debug("{}", dmnResult); + assertThat(dmnResult.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnResult.getMessages())).isFalse(); + + ZonedDateTime expected = ZonedDateTime.of(LocalDate.of(2021, 05, 31), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + assertThat(dmnResult.getDecisionResultByName("usingNormal").getResult()).isEqualTo(expected); + } + @Test public void testFiltering() { final DMNRuntime runtime = DMNRuntimeUtil.createRuntime("Person_filtering_by_age.dmn", getClass() ); diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNStronglyTypedSupportTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNStronglyTypedSupportTest.java index 24dc9de7b42..bb9ab591bd6 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNStronglyTypedSupportTest.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNStronglyTypedSupportTest.java @@ -165,6 +165,7 @@ public void testDateAndTime() { assertThat(ctx.get("Date-Time")).isEqualTo(ZonedDateTime.of(2016, 12, 24, 23, 59, 0, 0, ZoneOffset.ofHours(-5))); assertThat(ctx.get("Date")).isEqualTo(new HashMap() {{ put("fromString", LocalDate.of(2015, 12, 24)); + put( "fromStringToDateTime", ZonedDateTime.of( 2015, 12, 24, 0, 0, 0, 0, ZoneOffset.UTC) ); put("fromDateTime", LocalDate.of(2016, 12, 24)); put("fromYearMonthDay", LocalDate.of(1999, 11, 22)); }}); diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNTypeTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNTypeTest.java index 3c603b87836..96e8e2a7eda 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNTypeTest.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/DMNTypeTest.java @@ -93,7 +93,7 @@ public void testAllowedValuesForASimpleTypeCollection() { final String testNS = "testDROOLS2357"; final FEEL feel = FEEL.newInstance(); - final DMNType tDecision1 = typeRegistry.registerType(new SimpleTypeImpl(testNS, "tListOfVowels", null, true, feel.evaluateUnaryTests("\"a\",\"e\",\"i\",\"o\",\"u\""), FEEL_STRING, BuiltInType.STRING)); + final DMNType tDecision1 = typeRegistry.registerType(new SimpleTypeImpl(testNS, "tListOfVowels", null, true, feel.evaluateUnaryTests("\"a\",\"e\",\"i\",\"o\",\"u\""), null, FEEL_STRING, BuiltInType.STRING)); assertThat(tDecision1.isAssignableValue("a")).isTrue(); assertThat(tDecision1.isAssignableValue(Collections.singletonList("a"))).isTrue(); @@ -105,5 +105,21 @@ public void testAllowedValuesForASimpleTypeCollection() { assertThat(tDecision1.isAssignableValue(Arrays.asList("a", "e", "zzz"))).isFalse(); } + @Test + public void testTypeConstraintForASimpleTypeCollection() { + // incubator-kie-issues#926 + final String testNS = "testINCUBATORKIEISSUES926"; + + final FEEL feel = FEEL.newInstance(); + final DMNType tDecision1 = typeRegistry.registerType(new SimpleTypeImpl(testNS, "tListOfStrings", null, true, null, feel.evaluateUnaryTests("count (?) > 1"), FEEL_STRING, BuiltInType.LIST)); + + assertThat(tDecision1.isAssignableValue("asdvfsd")).isFalse(); + assertThat(tDecision1.isAssignableValue("zds")).isFalse(); + + assertThat(tDecision1.isAssignableValue(Collections.singletonList("asdfsd"))).isFalse(); + assertThat(tDecision1.isAssignableValue(Arrays.asList("ae", "efew"))).isTrue(); + assertThat(tDecision1.isAssignableValue(Arrays.asList("sda", "de", "z"))).isTrue(); + } + } diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/ast/DMNContextEvaluatorTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/ast/DMNContextEvaluatorTest.java new file mode 100644 index 00000000000..a7183d95c05 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/ast/DMNContextEvaluatorTest.java @@ -0,0 +1,76 @@ +package org.kie.dmn.core.ast; + +import java.io.File; +import java.math.BigDecimal; +import java.util.stream.Collectors; + +import org.drools.util.FileUtils; +import org.junit.Test; +import org.kie.dmn.api.core.DMNContext; +import org.kie.dmn.api.core.DMNModel; +import org.kie.dmn.api.core.DMNRuntime; +import org.kie.dmn.api.core.ast.DecisionNode; +import org.kie.dmn.core.api.DMNExpressionEvaluator; +import org.kie.dmn.core.api.DMNFactory; +import org.kie.dmn.core.api.EvaluatorResult; +import org.kie.dmn.core.impl.DMNDecisionResultImpl; +import org.kie.dmn.core.impl.DMNResultImpl; +import org.kie.dmn.core.impl.DMNResultImplFactory; +import org.kie.dmn.core.util.DMNRuntimeUtil; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class DMNContextEvaluatorTest { + + private DMNResultImplFactory dmnResultFactory = new DMNResultImplFactory(); + + @Test + public void dateToDateTime() { + File file = FileUtils.getFile("0007-date-time.dmn"); + final DMNRuntime runtime = DMNRuntimeUtil.createRuntime(file); + final DMNModel dmnModel = runtime.getModel("http://www.trisotech.com/definitions/_69430b3e-17b8-430d-b760-c505bf6469f9", "dateTime Table 58"); + assertThat(dmnModel).isNotNull(); + assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse(); + + DecisionNode date = dmnModel.getDecisionByName("Date"); + DMNExpressionEvaluator dateDecisionEvaluator = ((DecisionNodeImpl) date).getEvaluator(); + DMNContextEvaluator.ContextEntryDef ed = ((DMNContextEvaluator) dateDecisionEvaluator).getEntries().stream() + .filter(entry -> entry.getName().equals("fromStringToDateTime")) + .findFirst() + .orElseThrow(() -> new RuntimeException("Failed to find fromStringToDateTime ContextEntryDef")); + final DMNContext context = DMNFactory.newContext(); + context.set( "dateString", "2015-12-24" ); + context.set( "timeString", "00:00:01-01:00" ); + context.set( "dateTimeString", "2016-12-24T23:59:00-05:00" ); + context.set( "Hours", 12 ); + context.set( "Minutes", 59 ); + context.set( "Seconds", new BigDecimal("1.3" ) ); + context.set( "Timezone", "PT-1H" ); + context.set( "Year", 1999 ); + context.set( "Month", 11 ); + context.set( "Day", 22 ); + context.set( "durationString", "P13DT2H14S" ); // + DMNResultImpl result = createResult(dmnModel, context ); + DMNExpressionEvaluator evaluator = ed.getEvaluator(); + EvaluatorResult evaluated = evaluator.evaluate(runtime, result); + assertNotNull(evaluated); + assertEquals(EvaluatorResult.ResultType.SUCCESS, evaluated.getResultType()); + } + + private DMNResultImpl createResult(DMNModel model, DMNContext context) { + DMNResultImpl result = createResultImpl(model, context); + + for (DecisionNode decision : model.getDecisions().stream().filter(d -> d.getModelNamespace().equals(model.getNamespace())).collect(Collectors.toSet())) { + result.addDecisionResult(new DMNDecisionResultImpl(decision.getId(), decision.getName())); + } + return result; + } + + private DMNResultImpl createResultImpl(DMNModel model, DMNContext context) { + DMNResultImpl result = dmnResultFactory.newDMNResultImpl(model); + result.setContext(context.clone()); + return result; + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..f92c8a1c933 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/compiler/UnnamedImportUtilsTest.java @@ -0,0 +1,118 @@ +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.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; +import org.kie.dmn.backend.marshalling.v1x.DMNMarshallerFactory; +import org.kie.dmn.core.impl.DMNModelImpl; +import org.kie.dmn.core.util.DMNRuntimeUtil; +import org.kie.dmn.model.api.Definitions; +import org.kie.dmn.model.api.NamedElement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.addIfNotPresent; +import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport; + +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 DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + "Imported Model"); + assertThat(importedModel).isNotNull(); + final DMNModelImpl importingModel = (DMNModelImpl)runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", + "Importing empty-named Model"); + assertThat(importingModel).isNotNull(); + importedModel.getDecisions().forEach(node -> assertTrue(isInUnnamedImport(node, importingModel))); + importedModel.getBusinessKnowledgeModels().forEach(node -> assertTrue(isInUnnamedImport(node, importingModel))); + importedModel.getDecisionServices().forEach(node -> assertTrue(isInUnnamedImport(node, importingModel))); + importedModel.getInputs().forEach(node -> assertTrue(isInUnnamedImport(node, importingModel))); + importedModel.getItemDefinitions().forEach(node -> assertTrue(isInUnnamedImport(node, importingModel))); + } + + @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 DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36", + "Imported Model"); + assertThat(importedModel).isNotNull(); + final DMNModelImpl importingModel = (DMNModelImpl)runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc", + "Importing named Model"); + assertThat(importingModel).isNotNull(); + importedModel.getDecisions().forEach(node -> assertFalse(isInUnnamedImport(node, importingModel))); + importedModel.getBusinessKnowledgeModels().forEach(node -> assertFalse(isInUnnamedImport(node, importingModel))); + importedModel.getDecisionServices().forEach(node -> assertFalse(isInUnnamedImport(node, importingModel))); + importedModel.getInputs().forEach(node -> assertFalse(isInUnnamedImport(node, importingModel))); + importedModel.getItemDefinitions().forEach(node -> assertFalse(isInUnnamedImport(node, importingModel))); + } + + @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))); + } + + @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 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))); + } + + private boolean added(T source) { + return added(new ArrayList<>(), source); + } + + private boolean added(Collection target, T source) { + addIfNotPresent(target, source); + return target.contains(source); + } + +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/CoerceUtilTest.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/CoerceUtilTest.java new file mode 100644 index 00000000000..c2d829b9b00 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/CoerceUtilTest.java @@ -0,0 +1,186 @@ +package org.kie.dmn.core.util; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; + +import org.junit.Test; +import org.kie.dmn.api.core.DMNType; +import org.kie.dmn.core.impl.SimpleTypeImpl; +import org.kie.dmn.feel.lang.types.BuiltInType; + +import static org.junit.Assert.*; + +public class CoerceUtilTest { + + @Test + public void coerceValueCollectionToArrayConverted() { + Object item = "TESTED_OBJECT"; + Object value = Collections.singleton(item); + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "string", + null, + false, + null, + null, + null, + BuiltInType.STRING); + Object retrieved = CoerceUtil.coerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(item, retrieved); + } + + @Test + public void coerceValueCollectionToArrayNotConverted() { + Object item = "TESTED_OBJECT"; + Object value = Collections.singleton(item); + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "string", + null, + true, + null, + null, + null, + BuiltInType.STRING); + Object retrieved = CoerceUtil.coerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + + value = "TESTED_OBJECT"; + requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "string", + null, + false, + null, + null, + null, + BuiltInType.STRING); + retrieved = CoerceUtil.coerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + + requiredType = null; + retrieved = CoerceUtil.coerceValue(requiredType, value); + assertEquals(value, retrieved); + + value = null; + requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "string", + null, + false, + null, + null, + null, + BuiltInType.STRING); + retrieved = CoerceUtil.coerceValue(requiredType, value); + assertEquals(value, retrieved); + + } + + @Test + public void coerceValueDateToDateTimeConverted() { + Object value = LocalDate.now(); + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "date and time", + null, + false, + null, + null, + null, + BuiltInType.DATE_TIME); + Object retrieved = CoerceUtil.coerceValue(requiredType, value); + assertNotNull(retrieved); + assertTrue(retrieved instanceof ZonedDateTime); + ZonedDateTime zdtRetrieved = (ZonedDateTime)retrieved; + assertEquals(value, zdtRetrieved.toLocalDate()); + assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset()); + assertEquals(0, zdtRetrieved.getHour()); + assertEquals(0, zdtRetrieved.getMinute()); + assertEquals(0, zdtRetrieved.getSecond()); + } + + @Test + public void coerceValueDateToDateTimeNotConverted() { + Object value = "TEST_OBJECT"; + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "date and time", + null, + false, + null, + null, + null, + BuiltInType.DATE_TIME); + Object retrieved = CoerceUtil.coerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + value = LocalDate.now(); + requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "date", + null, + false, + null, + null, + null, + BuiltInType.DATE); + retrieved = CoerceUtil.coerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + } + + @Test + public void actualCoerceValueCollectionToArray() { + Object item = "TESTED_OBJECT"; + Object value = Collections.singleton(item); + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "string", + null, + false, + null, + null, + null, + BuiltInType.STRING); + Object retrieved = CoerceUtil.actualCoerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(item, retrieved); + } + + @Test + public void actualCoerceValueDateToDateTime() { + Object value = LocalDate.now(); + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "date and time", + null, + false, + null, + null, + null, + BuiltInType.DATE_TIME); + Object retrieved = CoerceUtil.actualCoerceValue(requiredType, value); + assertNotNull(retrieved); + assertTrue(retrieved instanceof ZonedDateTime); + ZonedDateTime zdtRetrieved = (ZonedDateTime)retrieved; + assertEquals(value, zdtRetrieved.toLocalDate()); + assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset()); + assertEquals(0, zdtRetrieved.getHour()); + assertEquals(0, zdtRetrieved.getMinute()); + assertEquals(0, zdtRetrieved.getSecond()); + } + + @Test + public void actualCoerceValueNotConverted() { + Object value = BigDecimal.valueOf(1L); + DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/", + "number", + null, + false, + null, + null, + null, + BuiltInType.NUMBER); + Object retrieved = CoerceUtil.actualCoerceValue(requiredType, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + } +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/DMNRuntimeUtil.java b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/DMNRuntimeUtil.java index 93624b4e078..90d8b374204 100644 --- a/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/DMNRuntimeUtil.java +++ b/kie-dmn/kie-dmn-core/src/test/java/org/kie/dmn/core/util/DMNRuntimeUtil.java @@ -18,6 +18,7 @@ */ package org.kie.dmn.core.util; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -72,10 +73,15 @@ public static DMNRuntime createRuntime(final String resourceName, final Class te final KieContainer kieContainer = KieHelper.getKieContainer( ks.newReleaseId("org.kie", "dmn-test-"+UUID.randomUUID(), "1.0"), ks.getResources().newClassPathResource(resourceName, testClass)); + return createRuntime(kieContainer); + } - final DMNRuntime runtime = typeSafeGetKieRuntime(kieContainer); - assertThat(runtime).isNotNull(); - return runtime; + public static DMNRuntime createRuntime(final File resourceFile) { + final KieServices ks = KieServices.Factory.get(); + final KieContainer kieContainer = KieHelper.getKieContainer( + ks.newReleaseId("org.kie", "dmn-test-"+UUID.randomUUID(), "1.0"), + ks.getResources().newFileSystemResource(resourceFile)); + return createRuntime(kieContainer); } public static List createExpectingDMNMessages(final String resourceName, final Class testClass) { @@ -111,6 +117,23 @@ public static DMNRuntime createRuntimeWithAdditionalResources(final String resou return runtime; } + public static DMNRuntime createRuntimeWithAdditionalResources(final File resourceFile, final File... additionalResourceFiles) { + final KieServices ks = KieServices.Factory.get(); + Resource mainResource = ks.getResources().newFileSystemResource(resourceFile); + List totalResources = new ArrayList<>(); + totalResources.add(mainResource); + for ( File add : additionalResourceFiles ) { + totalResources.add( ks.getResources().newFileSystemResource(add) ); + } + final KieContainer kieContainer = KieHelper.getKieContainer( + ks.newReleaseId("org.kie", "dmn-test-"+UUID.randomUUID(), "1.0"), + totalResources.toArray(new Resource[] {})); + + final DMNRuntime runtime = typeSafeGetKieRuntime(kieContainer); + assertThat(runtime).isNotNull(); + return runtime; + } + public static DMNRuntime typeSafeGetKieRuntime(final KieContainer kieContainer) { DMNRuntime dmnRuntime = kieContainer.newKieSession().getKieRuntime(DMNRuntime.class); ((DMNRuntimeImpl) dmnRuntime).setOption(new RuntimeTypeCheckOption(true)); @@ -192,4 +215,10 @@ public static byte[] createJarIgnoringErrors(KieServices ks, ReleaseId releaseId byte[] jar = kieModule.getBytes(); return jar; } + + static DMNRuntime createRuntime(KieContainer kieContainer) { + final DMNRuntime runtime = typeSafeGetKieRuntime(kieContainer); + assertThat(runtime).isNotNull(); + return runtime; + } } diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0007-date-time.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0007-date-time.dmn index 103fabd9423..8f765d01141 100644 --- a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0007-date-time.dmn +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/0007-date-time.dmn @@ -72,6 +72,12 @@ date(dateString) + + + + date(dateString) + + diff --git a/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/DateToDateTimeFunction.dmn b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/DateToDateTimeFunction.dmn new file mode 100644 index 00000000000..b947dfd7f7c --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/DateToDateTimeFunction.dmn @@ -0,0 +1,24 @@ + + + + + + + + + + a+b + + + + + + + + + + + normal( @"2019-03-31", duration( "P26M" )) + + + 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 new file mode 100644 index 00000000000..d32261f9d46 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Imported_Model_Unamed.dmn @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..ff2d5d27201 --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/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-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 new file mode 100644 index 00000000000..e83912d76fc --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/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-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 new file mode 100644 index 00000000000..63ef659ae0c --- /dev/null +++ b/kie-dmn/kie-dmn-core/src/test/resources/org/kie/dmn/core/Importing_OverridingEmptyNamed_Model.dmn @@ -0,0 +1,93 @@ + + + + + + + + + + + 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/lang/types/AliasFEELType.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/AliasFEELType.java index a2856480a4a..09bea494c99 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/AliasFEELType.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/AliasFEELType.java @@ -49,6 +49,7 @@ public boolean isAssignableValue(Object value) { return wrapped.isAssignableValue(value); } + public BuiltInType getBuiltInType() { return wrapped; } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/GenListType.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/GenListType.java index 78f577fae08..30e5b6d357b 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/GenListType.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/types/GenListType.java @@ -25,8 +25,12 @@ public class GenListType implements SimpleType { + /** + * Represents the "generic" type of the current list + */ private final Type gen; + public GenListType(Type gen) { this.gen = gen; } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java index 64c76d89bbd..ae1fbf5e5f6 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java @@ -24,9 +24,9 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -46,6 +46,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.kie.dmn.feel.util.CoerceUtil.coerceParams; + public abstract class BaseFEELFunction implements FEELFunction { @@ -153,10 +155,7 @@ public Object invokeReflectively(EvaluationContext ctx, Object[] params) { } } catch ( Exception e ) { logger.error( "Error trying to call function " + getName() + ".", e ); - ctx.notifyEvt( () -> { - return new FEELEventBase( Severity.ERROR, "Error trying to call function " + getName() + ".", e ); - } - ); + ctx.notifyEvt( () -> new FEELEventBase( Severity.ERROR, "Error trying to call function " + getName() + ".", e )); } return null; } @@ -238,22 +237,12 @@ private CandidateMethod getCandidateMethod(EvaluationContext ctx, Object[] param boolean found = true; for ( int i = 0; i < parameterTypes.length; i++ ) { Class currentIdxActualParameterType = cm.getActualClasses()[i]; - if ( currentIdxActualParameterType != null && !parameterTypes[i].isAssignableFrom( currentIdxActualParameterType ) ) { - // singleton list spec defines that "a=[a]", i.e., singleton collections should be treated as the single element - // and vice-versa - if ( Collection.class.isAssignableFrom( currentIdxActualParameterType ) ) { - Collection valueCollection = (Collection) actualParams[i]; - if ( valueCollection.size() == 1 ) { - Object singletonValue = valueCollection.iterator().next(); - // re-perform the assignable-from check, this time using the element itself the singleton value from the original parameter list - if ( singletonValue != null && parameterTypes[i].isAssignableFrom( singletonValue.getClass() ) ) { - Object[] newParams = new Object[cm.getActualParams().length]; - System.arraycopy( cm.getActualParams(), 0, newParams, 0, cm.getActualParams().length ); // can't rely on adjustForVariableParameters() have actually copied - newParams[i] = singletonValue; - cm.setActualParams(newParams); - continue; - } - } + Class expectedParameterType = parameterTypes[i]; + if ( currentIdxActualParameterType != null && !expectedParameterType.isAssignableFrom( currentIdxActualParameterType ) ) { + Optional coercedParams = coerceParams(currentIdxActualParameterType, expectedParameterType, actualParams, i); + if (coercedParams.isPresent()) { + cm.setActualParams(coercedParams.get()); + continue; } found = false; break; diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateFunction.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateFunction.java index 37cd0b3bf29..05f28b8bfde 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateFunction.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/DateFunction.java @@ -62,7 +62,7 @@ public FEELFnResult invoke(@ParameterName( "from" ) String val if (!BEGIN_YEAR.matcher(val).find()) { // please notice the regex strictly requires the beginning, so we can use find. return FEELFnResult.ofError(new InvalidParametersEvent(Severity.ERROR, "from", "year not compliant with XML Schema Part 2 Datatypes")); } - + try { return FEELFnResult.ofResult(LocalDate.from(FEEL_DATE.parse(val))); } catch (DateTimeException e) { @@ -80,7 +80,7 @@ public FEELFnResult invoke(@ParameterName( "year" ) Number yea if ( day == null ) { return FEELFnResult.ofError(new InvalidParametersEvent(Severity.ERROR, "day", "cannot be null")); } - + try { return FEELFnResult.ofResult( LocalDate.of( year.intValue(), month.intValue(), day.intValue() ) ); } catch (DateTimeException e) { @@ -92,7 +92,7 @@ public FEELFnResult invoke(@ParameterName( "from" ) TemporalAc if ( date == null ) { return FEELFnResult.ofError(new InvalidParametersEvent(Severity.ERROR, "from", "cannot be null")); } - + try { return FEELFnResult.ofResult( LocalDate.from( date ) ); } catch (DateTimeException e) { diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java new file mode 100644 index 00000000000..4672de1b397 --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java @@ -0,0 +1,90 @@ +/** + * 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.feel.util; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Optional; + +import org.kie.dmn.feel.lang.Type; +import org.kie.dmn.feel.lang.types.BuiltInType; + +/** + * Class used to centralize all coercion-related behavior + */ +public class CoerceUtil { + + private CoerceUtil() { + // singleton class + } + + public static Object coerceParameter(Type requiredType, Object valueToCoerce) { + return (requiredType != null && valueToCoerce != null) ? actualCoerceParameter(requiredType, valueToCoerce) : + valueToCoerce; + } + + public static Optional coerceParams(Class currentIdxActualParameterType, Class expectedParameterType, Object[] actualParams, int i) { + Object actualObject = actualParams[i]; + Optional coercedObject = coerceParam(currentIdxActualParameterType, expectedParameterType, + actualObject); + return coercedObject.map(o -> actualCoerceParams(actualParams, o, i)); + } + + static Optional coerceParam(Class currentIdxActualParameterType, Class expectedParameterType, + Object actualObject) { + if (Collection.class.isAssignableFrom(currentIdxActualParameterType)) { + Collection valueCollection = (Collection) actualObject; + if (valueCollection.size() == 1) { + Object singletonValue = valueCollection.iterator().next(); + // re-perform the assignable-from check, this time using the element itself the singleton value from + // the original parameter list + if (singletonValue != null) { + return expectedParameterType.isAssignableFrom(singletonValue.getClass()) ? + Optional.of(singletonValue) : + coerceParam(singletonValue.getClass(), expectedParameterType, singletonValue); + } + } + } + if (actualObject instanceof LocalDate localDate && + ZonedDateTime.class.isAssignableFrom(expectedParameterType)) { + Object coercedObject = EvalHelper.coerceDateTime(localDate); + return Optional.of(coercedObject); + } + return Optional.empty(); + } + + static Object actualCoerceParameter(Type requiredType, Object valueToCoerce) { + Object toReturn = valueToCoerce; + if (valueToCoerce instanceof LocalDate localDate && + requiredType == BuiltInType.DATE_TIME) { + return EvalHelper.coerceDateTime(localDate); + } + return toReturn; + } + + static Object[] actualCoerceParams(Object[] actualParams, Object coercedObject, int i) { + Object[] toReturn = new Object[actualParams.length]; + System.arraycopy( actualParams, 0, toReturn, 0, actualParams.length ); + toReturn[i] = coercedObject; + return toReturn; + } + + +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java index 3e4a3e00da7..452b3b2db19 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java @@ -24,7 +24,9 @@ import java.math.BigInteger; import java.math.MathContext; import java.time.Duration; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; @@ -191,6 +193,10 @@ public static Object coerceNumber(Object value) { } } + public static ZonedDateTime coerceDateTime(final LocalDate value) { + return ZonedDateTime.of(value, LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + } + public static Boolean getBooleanOrNull(Object value) { if (!(value instanceof Boolean)) { return null; @@ -281,6 +287,11 @@ public static String unescapeString(String text) { public static class PropertyValueResult implements FEELPropertyAccessible.AbstractPropertyValueResult { + // This exception is used to signal an undefined property for notDefined(). This method may be many times when + // evaluating a decision, so a single instance is being cached to avoid the cost of creating the stack trace + // each time. + private static final Exception undefinedPropertyException = new UnsupportedOperationException("Property was not defined."); + private final boolean defined; private final Either valueResult; @@ -290,7 +301,7 @@ private PropertyValueResult(boolean isDefined, Either value) } public static PropertyValueResult notDefined() { - return new PropertyValueResult(false, Either.ofLeft(new UnsupportedOperationException("Property was not defined."))); + return new PropertyValueResult(false, Either.ofLeft(undefinedPropertyException)); } public static PropertyValueResult of(Either valueResult) { @@ -776,4 +787,4 @@ public String toString() { '}'; } } -} \ No newline at end of file +} diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java index 5478e9a575f..a84998a33ca 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java @@ -187,7 +187,7 @@ public static Collection data() { { "all( true, true )", true, null}, { "all( [true, false] )", false, null}, { "all( [true, true] )", true, null}, - { "all( [false,null,true] )", false, null}, + { "all( [false,null,true] )", false, null}, { "all( [] )", true, null}, { "all( 0 )", null, FEELEvent.Severity.ERROR}, { "all( )", null, FEELEvent.Severity.ERROR}, @@ -199,32 +199,32 @@ public static Collection data() { { "any( true, true )", true, null}, { "any( [true, false] )", true, null}, { "any( [true, true] )", true, null}, - { "any( [false,null,true] )", true, null}, + { "any( [false,null,true] )", true, null}, { "any( [] )", false, null}, { "any( 0 )", null, FEELEvent.Severity.ERROR}, { "any( )", null, FEELEvent.Severity.ERROR}, - + { "day of year( date(2019, 9, 17) )", BigDecimal.valueOf( 260 ), null}, { "day of week( date(2019, 9, 17) )", "Tuesday", null}, { "month of year( date(2019, 9, 17) )", "September", null}, { "week of year( date(2019, 9, 17) )", BigDecimal.valueOf( 38 ), null}, { "week of year( date(2003, 12, 29) )", BigDecimal.valueOf( 1 ), null}, // ISO defs. - { "week of year( date(2004, 1, 4) )", BigDecimal.valueOf( 1 ), null}, - { "week of year( date(2005, 1, 3) )", BigDecimal.valueOf( 1 ), null}, - { "week of year( date(2005, 1, 9) )", BigDecimal.valueOf( 1 ), null}, - { "week of year( date(2005, 1, 1) )", BigDecimal.valueOf( 53 ), null}, + { "week of year( date(2004, 1, 4) )", BigDecimal.valueOf( 1 ), null}, + { "week of year( date(2005, 1, 3) )", BigDecimal.valueOf( 1 ), null}, + { "week of year( date(2005, 1, 9) )", BigDecimal.valueOf( 1 ), null}, + { "week of year( date(2005, 1, 1) )", BigDecimal.valueOf( 53 ), null}, { "median( 8, 2, 5, 3, 4 )", new BigDecimal("4") , null}, { "median( [6, 1, 2, 3] )", new BigDecimal("2.5") , null}, { "median( [ ] ) ", null, null}, // DMN spec, Table 69: Semantics of list functions - + { "0-max( 1, 2, 3 )", BigDecimal.valueOf( -3 ) , null}, { "-max( 1, 2, 3 )", BigDecimal.valueOf( -3 ) , null}, // DROOLS-5981 { "0-sum( 1, 2, 3 )", BigDecimal.valueOf( -6 ) , null}, { "-sum( 1, 2, 3 )", BigDecimal.valueOf( -6 ) , null}, { "0-abs( 10 )", new BigDecimal("-10") , null}, - { "-abs( 10 )", new BigDecimal("-10") , null}, + { "-abs( 10 )", new BigDecimal("-10") , null}, { "0-max( 1, abs(-2), 3 )", BigDecimal.valueOf( -3 ) , null}, - { "-max( 1, abs(-2), 3 )", BigDecimal.valueOf( -3 ) , null}, + { "-max( 1, abs(-2), 3 )", BigDecimal.valueOf( -3 ) , null}, { "0-max( 1, -abs(-2), 3 )", BigDecimal.valueOf( -3 ) , null}, { "-max( 1, -abs(-2), 3 )", BigDecimal.valueOf( -3 ) , null}, { "{a: 2, r: 0-sum( 1, a, 3 )}.r", BigDecimal.valueOf( -6 ) , null}, diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java new file mode 100644 index 00000000000..28413e6d1ab --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java @@ -0,0 +1,148 @@ +package org.kie.dmn.feel.util; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import org.junit.Test; +import org.kie.dmn.feel.lang.types.BuiltInType; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class CoerceUtilTest { + + @Test + public void coerceParameterDateToDateTimeConverted() { + Object value = LocalDate.now(); + Object retrieved = CoerceUtil.coerceParameter(BuiltInType.DATE_TIME, value); + assertNotNull(retrieved); + assertTrue(retrieved instanceof ZonedDateTime); + ZonedDateTime zdtRetrieved = (ZonedDateTime) retrieved; + assertEquals(value, zdtRetrieved.toLocalDate()); + assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset()); + assertEquals(0, zdtRetrieved.getHour()); + assertEquals(0, zdtRetrieved.getMinute()); + assertEquals(0, zdtRetrieved.getSecond()); + } + + @Test + public void coerceParameterDateToDateTimeNotConverted() { + Object value = "TEST_OBJECT"; + Object retrieved = CoerceUtil.coerceParameter(null, value); + assertEquals(value, retrieved); + + value = null; + retrieved = CoerceUtil.coerceParameter(BuiltInType.DATE, value); + assertEquals(value, retrieved); + } + + @Test + public void coerceParamsCollectionToArrayConverted() { + Object item = "TESTED_OBJECT"; + Object value = Collections.singleton(item); + Object[] actualParams1 = {value, "NOT_DATE"}; + Optional retrieved = CoerceUtil.coerceParams(Set.class, String.class, actualParams1, 0); + assertNotNull(retrieved); + assertTrue(retrieved.isPresent()); + Object[] retrievedObjects = retrieved.get(); + assertEquals(item, retrievedObjects[0]); + assertEquals(actualParams1[1], retrievedObjects[1]); + + + item = LocalDate.now(); + value = Collections.singleton(item); + Object[] actualParams2 = {value, "NOT_DATE"}; + retrieved = CoerceUtil.coerceParams(Set.class, ZonedDateTime.class, actualParams2, 0); + assertNotNull(retrieved); + assertTrue(retrieved.isPresent()); + retrievedObjects = retrieved.get(); + assertTrue(retrievedObjects[0] instanceof ZonedDateTime); + ZonedDateTime zdtRetrieved = (ZonedDateTime) retrievedObjects[0]; + assertEquals(item, zdtRetrieved.toLocalDate()); + assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset()); + assertEquals(0, zdtRetrieved.getHour()); + assertEquals(0, zdtRetrieved.getMinute()); + assertEquals(0, zdtRetrieved.getSecond()); + assertEquals(actualParams2[1], retrievedObjects[1]); + } + + @Test + public void coerceParamsToDateTimeConverted() { + Object value = LocalDate.now(); + Object[] actualParams = {value, "NOT_DATE"}; + Optional retrieved = CoerceUtil.coerceParams(LocalDate.class, ZonedDateTime.class, actualParams, 0); + assertNotNull(retrieved); + assertTrue(retrieved.isPresent()); + Object[] retrievedObjects = retrieved.get(); + assertTrue(retrievedObjects[0] instanceof ZonedDateTime); + ZonedDateTime zdtRetrieved = (ZonedDateTime) retrievedObjects[0]; + assertEquals(value, zdtRetrieved.toLocalDate()); + assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset()); + assertEquals(0, zdtRetrieved.getHour()); + assertEquals(0, zdtRetrieved.getMinute()); + assertEquals(0, zdtRetrieved.getSecond()); + assertEquals(actualParams[1], retrievedObjects[1]); + } + + @Test + public void coerceParamsNotConverted() { + Object item = "TESTED_OBJECT"; + Object value = Collections.singleton(item); + Object[] actualParams1 = {value, "NOT_DATE"}; + Optional retrieved = CoerceUtil.coerceParams(Set.class, BigDecimal.class, actualParams1, 0); + assertNotNull(retrieved); + assertTrue(retrieved.isEmpty()); + + value = LocalDate.now(); + Object[] actualParams2 = {value, "NOT_DATE"}; + retrieved = CoerceUtil.coerceParams(LocalDate.class, String.class, actualParams2, 0); + assertNotNull(retrieved); + assertTrue(retrieved.isEmpty()); + } + + @Test + public void actualCoerceParameterToDateTimeConverted() { + Object value = LocalDate.now(); + Object retrieved = CoerceUtil.actualCoerceParameter(BuiltInType.DATE_TIME, value); + assertNotNull(retrieved); + assertTrue(retrieved instanceof ZonedDateTime); + ZonedDateTime zdtRetrieved = (ZonedDateTime) retrieved; + assertEquals(value, zdtRetrieved.toLocalDate()); + assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset()); + assertEquals(0, zdtRetrieved.getHour()); + assertEquals(0, zdtRetrieved.getMinute()); + assertEquals(0, zdtRetrieved.getSecond()); + } + + @Test + public void actualCoerceParameterNotConverted() { + Object value = "TEST_OBJECT"; + Object retrieved = CoerceUtil.actualCoerceParameter(BuiltInType.DATE_TIME, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + + value = LocalDate.now(); + retrieved = CoerceUtil.actualCoerceParameter(BuiltInType.DATE, value); + assertNotNull(retrieved); + assertEquals(value, retrieved); + } + + @Test + public void actualCoerceParams() { + Object value = LocalDate.now(); + Object[] actualParams = {value, "NOT_DATE"}; + Object coercedValue = BigDecimal.valueOf(1L); + Object[] retrieved = CoerceUtil.actualCoerceParams(actualParams, coercedValue, 0); + assertNotNull(retrieved); + assertEquals(actualParams.length, retrieved.length); + assertEquals(coercedValue, retrieved[0]); + assertEquals(actualParams[1], retrieved[1]); + } + +} \ No newline at end of file diff --git a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/ItemDefinition.java b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/ItemDefinition.java index de498f77eee..3bd7be4f935 100644 --- a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/ItemDefinition.java +++ b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/api/ItemDefinition.java @@ -38,9 +38,15 @@ public interface ItemDefinition extends NamedElement { void setAllowedValues(UnaryTests value); - UnaryTests getTypeConstraint(); - - void setTypeConstraint(UnaryTests value); + default UnaryTests getTypeConstraint() { + // Since DMN 1.5 + return null; + } + + default void setTypeConstraint(UnaryTests value) { + // Since DMN 1.5 + // no op + } List getItemComponent(); diff --git a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_1/TItemDefinition.java b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_1/TItemDefinition.java index a017354d89b..9ca8d6664a0 100644 --- a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_1/TItemDefinition.java +++ b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_1/TItemDefinition.java @@ -56,16 +56,6 @@ public void setAllowedValues(final UnaryTests value) { this.allowedValues = value; } - @Override - public UnaryTests getTypeConstraint() { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - - @Override - public void setTypeConstraint(UnaryTests value) { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - @Override public List getItemComponent() { return this.itemComponent; diff --git a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_2/TItemDefinition.java b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_2/TItemDefinition.java index 899ca184203..2f584fda419 100644 --- a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_2/TItemDefinition.java +++ b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_2/TItemDefinition.java @@ -58,16 +58,6 @@ public void setAllowedValues(UnaryTests value) { this.allowedValues = value; } - @Override - public UnaryTests getTypeConstraint() { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - - @Override - public void setTypeConstraint(UnaryTests value) { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - @Override public List getItemComponent() { if (itemComponent == null) { diff --git a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_3/TItemDefinition.java b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_3/TItemDefinition.java index 347847b2597..06c02feee28 100644 --- a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_3/TItemDefinition.java +++ b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_3/TItemDefinition.java @@ -59,16 +59,6 @@ public void setAllowedValues(UnaryTests value) { this.allowedValues = value; } - @Override - public UnaryTests getTypeConstraint() { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - - @Override - public void setTypeConstraint(UnaryTests value) { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - @Override public List getItemComponent() { if (itemComponent == null) { diff --git a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_4/TItemDefinition.java b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_4/TItemDefinition.java index 1e414aa0768..c3d11455279 100644 --- a/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_4/TItemDefinition.java +++ b/kie-dmn/kie-dmn-model/src/main/java/org/kie/dmn/model/v1_4/TItemDefinition.java @@ -59,16 +59,6 @@ public void setAllowedValues(UnaryTests value) { this.allowedValues = value; } - @Override - public UnaryTests getTypeConstraint() { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - - @Override - public void setTypeConstraint(UnaryTests value) { - throw new UnsupportedOperationException("Since DMNv1.5"); - } - @Override public List getItemComponent() { if (itemComponent == null) { diff --git a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASConstants.java b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASConstants.java index cb36347900a..527a1d6545d 100644 --- a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASConstants.java +++ b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASConstants.java @@ -22,6 +22,7 @@ public class DMNOASConstants { public static final String X_DMN_TYPE = "x-dmn-type"; public static final String X_DMN_ALLOWED_VALUES = "x-dmn-allowed-values"; + public static final String X_DMN_TYPE_CONSTRAINTS = "x-dmn-type-constraints"; public static final String X_DMN_DESCRIPTIONS = "x-dmn-descriptions"; private DMNOASConstants() { diff --git a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASGeneratorImpl.java b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASGeneratorImpl.java index bd006c1dfaf..1e948f09ce5 100644 --- a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASGeneratorImpl.java +++ b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNOASGeneratorImpl.java @@ -18,16 +18,6 @@ */ package org.kie.dmn.openapi.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; - import com.fasterxml.jackson.databind.node.ObjectNode; import io.smallrye.openapi.runtime.io.JsonUtil; import io.smallrye.openapi.runtime.io.schema.SchemaWriter; @@ -44,6 +34,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + public class DMNOASGeneratorImpl implements DMNOASGenerator { private static final Logger LOG = LoggerFactory.getLogger(DMNOASGeneratorImpl.class); private final List dmnModels; @@ -81,7 +82,10 @@ private void prepareSerializaton() { ObjectNode tree = JsonUtil.objectNode(); ObjectNode definitions = JsonUtil.objectNode(); tree.set("definitions", definitions); - for (Entry kv : schemas.entrySet()) { + // It would be better if the map is a TreeMap, however that breaks test ProcessItemTest.test_together + // For some reason, it looks like there is some reliance on the map being a HashMap, which should be investigated later as that should never happen. + final List> sortedEntries = schemas.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.comparing(DMNType::getName))).toList(); + for (Entry kv : sortedEntries) { SchemaWriter.writeSchema(definitions, kv.getValue(), namingPolicy.getName(kv.getKey())); } jsonSchema = tree; @@ -191,5 +195,4 @@ private void visitForIndexing(DMNType idnType) { } } } - } diff --git a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java index 1e9ac0703f2..94706266053 100644 --- a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java +++ b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java @@ -32,6 +32,7 @@ import org.eclipse.microprofile.openapi.models.media.Schema; import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType; import org.kie.dmn.api.core.DMNType; +import org.kie.dmn.api.core.DMNUnaryTest; import org.kie.dmn.core.impl.BaseDMNTypeImpl; import org.kie.dmn.core.impl.CompositeTypeImpl; import org.kie.dmn.core.impl.SimpleTypeImpl; @@ -127,12 +128,10 @@ private Schema schemaFromSimpleType(SimpleTypeImpl t) { } Schema schema = refOrBuiltinSchema(baseType); if (t.getAllowedValues() != null && !t.getAllowedValues().isEmpty()) { - schema.addExtension(DMNOASConstants.X_DMN_ALLOWED_VALUES, t.getAllowedValuesFEEL().stream().map(UnaryTest::toString).collect(Collectors.joining(", "))); - if (DMNTypeUtils.getFEELBuiltInType(ancestor(t)) == BuiltInType.NUMBER) { - FEELSchemaEnum.parseNumberAllowedValuesIntoSchema(schema, t.getAllowedValues()); - } else { - FEELSchemaEnum.parseAllowedValuesIntoSchema(schema, t.getAllowedValues()); - } + parseSimpleType(DMNOASConstants.X_DMN_ALLOWED_VALUES, t, schema, t.getAllowedValuesFEEL(), t.getAllowedValues()); + } + if (t.getTypeConstraint() != null && !t.getTypeConstraint().isEmpty()) { + parseSimpleType(DMNOASConstants.X_DMN_TYPE_CONSTRAINTS, t, schema, t.getTypeConstraintFEEL(), t.getAllowedValues()); } schema = nestAsItemIfCollection(schema, t); schema.addExtension(X_DMN_TYPE, getDMNTypeSchemaXDMNTYPEdescr(t)); @@ -140,6 +139,15 @@ private Schema schemaFromSimpleType(SimpleTypeImpl t) { return schema; } + private void parseSimpleType(String schemaString, SimpleTypeImpl t, Schema schema, List feelUnaryTests, List dmnUnaryTests) { + schema.addExtension(schemaString, feelUnaryTests.stream().map(UnaryTest::toString).collect(Collectors.joining(", "))); + if (DMNTypeUtils.getFEELBuiltInType(ancestor(t)) == BuiltInType.NUMBER) { + FEELSchemaEnum.parseNumbersIntoSchema(schema, dmnUnaryTests); + } else { + FEELSchemaEnum.parseValuesIntoSchema(schema, dmnUnaryTests); + } + } + private Schema schemaFromCompositeType(CompositeTypeImpl ct) { Schema schema = OASFactory.createObject(Schema.class).type(SchemaType.OBJECT); if (ct.getBaseType() == null) { // main case diff --git a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java index 25b31042595..30e2cb58138 100644 --- a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java +++ b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java @@ -36,7 +36,7 @@ public class FEELSchemaEnum { private static final Logger LOG = LoggerFactory.getLogger(FEELSchemaEnum.class); - public static void parseAllowedValuesIntoSchema(Schema schema, List list) { + public static void parseValuesIntoSchema(Schema schema, List list) { List expectLiterals = evaluateUnaryTests(list); if (expectLiterals.contains(null)) { schema.setNullable(true); @@ -45,11 +45,11 @@ public static void parseAllowedValuesIntoSchema(Schema schema, List list) { + public static void parseNumbersIntoSchema(Schema schema, List list) { List uts = evaluateUnaryTests(list); // we leverage the property of the *base* FEEL grammar(non visited by ASTVisitor, only the ParseTree->AST Visitor) that `>x` is a Range boolean allowNull = uts.remove(null); if (allowNull) { @@ -73,7 +73,7 @@ public static void parseNumberAllowedValuesIntoSchema(Schema schema, List - - - - - %date{HH:mm:ss.SSS} [%thread] %-5level %class{36}.%method:%line - %msg%n - - - - - - - - - - - - - + + 4.0.0 + + org.kie + kie-dmn + 999-SNAPSHOT + + + kie-dmn-test-resources + + KIE :: Decision Model Notation :: Test resources + Module to contain all DMN models used for tests + + + org.kie.dmn.test.resources + UTF-8 + + + \ No newline at end of file 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 new file mode 100644 index 00000000000..81b3738abfc --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/AllowedValuesChecksInsideCollection.dmn @@ -0,0 +1,58 @@ + + + + + string + + + tInterests + + + + string + + + tInterest + + "Golf","Computer","Hockey","Jogging" + + + + + + + + + "The Person " + p1.Name + " likes " + string(count( p1.Interests )) + " thing(s)." + + + + + + 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 new file mode 100644 index 00000000000..f2ece77a60a --- /dev/null +++ b/kie-dmn/kie-dmn-test-resources/src/test/resources/TypeConstraintsChecks.dmn @@ -0,0 +1,57 @@ + + + + + string + + + tInterests + + + + string + + + tInterest + + count (?) = 1 + + + + + + + + + "The Person " + p1.Name + " likes " + string(count( p1.Interests )) + " thing(s)." + + + + + + diff --git a/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/dtanalysis/DMNDTAnalyser.java b/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/dtanalysis/DMNDTAnalyser.java index 37521c58ae7..aaf4fbe81e1 100644 --- a/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/dtanalysis/DMNDTAnalyser.java +++ b/kie-dmn/kie-dmn-validation/src/main/java/org/kie/dmn/validation/dtanalysis/DMNDTAnalyser.java @@ -35,11 +35,11 @@ import org.kie.dmn.api.core.DMNModel; import org.kie.dmn.core.ast.DMNBaseNode; -import org.kie.dmn.core.compiler.DMNCompilerImpl; import org.kie.dmn.core.compiler.DMNProfile; import org.kie.dmn.core.impl.DMNModelImpl; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; +import org.kie.dmn.core.util.NamespaceUtil; import org.kie.dmn.feel.codegen.feel11.ProcessedExpression; import org.kie.dmn.feel.codegen.feel11.ProcessedUnaryTest; import org.kie.dmn.feel.lang.CompilerContext; @@ -177,7 +177,7 @@ private DTAnalysis dmnDTAnalysis(DMNModel model, DecisionTable dt, Setkie-dmn-core-jsr223-jq kie-dmn-core-jsr223 kie-dmn-ruleset2dmn-parent + kie-dmn-test-resources diff --git a/pom.xml b/pom.xml index 581c84bf958..7dcdf4c004e 100644 --- a/pom.xml +++ b/pom.xml @@ -210,7 +210,6 @@ kie-maven-plugin kie-archetypes drools-quarkus-extension - jpmml-migration-recipe drools-reliability drools-drlonyaml-parent