diff --git a/.ci/jenkins/tests/pom.xml b/.ci/jenkins/tests/pom.xml index 631f747b248..d790ede2107 100644 --- a/.ci/jenkins/tests/pom.xml +++ b/.ci/jenkins/tests/pom.xml @@ -40,7 +40,7 @@ 2.4.11 1.6.1 - 29.0-jre + 32.0.0-jre 2.0.0 2.334 diff --git a/drools/kogito-scenario-simulation/src/main/java/org/kogito/scenariosimulation/runner/KogitoDMNScenarioRunnerHelper.java b/drools/kogito-scenario-simulation/src/main/java/org/kogito/scenariosimulation/runner/KogitoDMNScenarioRunnerHelper.java index 9d6f96ef9eb..2babf303133 100644 --- a/drools/kogito-scenario-simulation/src/main/java/org/kogito/scenariosimulation/runner/KogitoDMNScenarioRunnerHelper.java +++ b/drools/kogito-scenario-simulation/src/main/java/org/kogito/scenariosimulation/runner/KogitoDMNScenarioRunnerHelper.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.stream.Stream; +import org.drools.codegen.common.AppPaths; import org.drools.io.FileSystemResource; import org.drools.scenariosimulation.api.model.ScenarioSimulationModel; import org.drools.scenariosimulation.api.model.ScesimModelDescriptor; @@ -53,7 +54,7 @@ public class KogitoDMNScenarioRunnerHelper extends DMNScenarioRunnerHelper { private DMNRuntime dmnRuntime = initDmnRuntime(); - private static final String targetFolder = File.separator + "target" + File.separator; + private static final String targetFolder = File.separator + AppPaths.TARGET_DIR + File.separator; private static final String generatedResourcesFolder = targetFolder + "generated-resources" + File.separator; @Override diff --git a/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/MigrationPlanService.java b/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/MigrationPlanService.java index 12951dfb169..662eefaf914 100644 --- a/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/MigrationPlanService.java +++ b/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/MigrationPlanService.java @@ -25,14 +25,21 @@ import org.jbpm.flow.migration.model.ProcessDefinitionMigrationPlan; import org.jbpm.ruleflow.instance.RuleFlowProcessInstance; import org.jbpm.workflow.instance.impl.NodeInstanceImpl; +import org.kie.kogito.Model; import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcessInstance; +import org.kie.kogito.process.Processes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.google.common.base.Functions.identity; import static java.util.stream.Collectors.toMap; +/** + * The migration system is limited in this way. + * Cannot have more that one identifier process deployed (version is fixed) + * if there are several migration plans defined for the same source only one is taken into account + */ public class MigrationPlanService { private static final Logger LOGGER = LoggerFactory.getLogger(MigrationPlanService.class); @@ -49,8 +56,8 @@ public MigrationPlanService(MigrationPlanProvider migrationPlanProvider) { this.migrations.putAll(this.migrationPlanProvider.findMigrationPlans().stream().collect(toMap(MigrationPlan::getSource, identity()))); } - public void migrateProcessElement(KogitoWorkflowProcessInstance processInstance) { - MigrationPlan plan = getMigrationPlan(processInstance); + public void migrateProcessElement(Processes processes, KogitoWorkflowProcessInstance processInstance) { + MigrationPlan plan = getMigrationPlan(processes, processInstance); if (plan != null) { // the process will have to do nothing as it is done by the engine itself LOGGER.info("Process instance {} will be migrated from {} to {} with plan {}", @@ -67,9 +74,9 @@ public void migrateProcessElement(KogitoWorkflowProcessInstance processInstance) } } - public void migrateNodeElement(KogitoNodeInstance nodeInstance) { + public void migrateNodeElement(Processes processes, KogitoNodeInstance nodeInstance) { KogitoWorkflowProcessInstance pi = (KogitoWorkflowProcessInstance) nodeInstance.getProcessInstance(); - MigrationPlan plan = getMigrationPlan(pi); + MigrationPlan plan = getMigrationPlan(processes, pi); if (plan == null) { return; } @@ -79,14 +86,63 @@ public void migrateNodeElement(KogitoNodeInstance nodeInstance) { impl.setNodeId(plan.getProcessMigrationPlan().getNodeMigratedFor(nodeInstance)); } - private MigrationPlan getMigrationPlan(KogitoWorkflowProcessInstance processInstance) { + // we check the target deployed in the container is the same as the target in the migration plan + private MigrationPlan getMigrationPlan(Processes processes, KogitoWorkflowProcessInstance processInstance) { + // first check if we need a migration as the process being set should be not be the same as the process set in the + // process being loaded. + String currentProcessId = processInstance.getProcess().getId(); + String currentVersion = processInstance.getProcess().getVersion(); + ProcessDefinitionMigrationPlan currentProcessDefinition = new ProcessDefinitionMigrationPlan(currentProcessId, currentVersion); + + RuleFlowProcessInstance pi = (RuleFlowProcessInstance) processInstance; + ProcessDefinitionMigrationPlan processStateDefinition = new ProcessDefinitionMigrationPlan(pi.getProcessId(), pi.getProcessVersion()); + + // check if definition and state match. we don't need to perform any migration. + if (currentProcessDefinition.equals(processStateDefinition)) { + return null; + } + + // there is no migration plan define for the source + MigrationPlan plan = migrations.get(processStateDefinition); + if (plan == null) { + LOGGER.debug("No migration plan defined for process state {}.", processStateDefinition); + return null; + } + + // current process definition matches the target process of the migration plan + ProcessDefinitionMigrationPlan targetDefinition = plan.getProcessMigrationPlan().getTargetProcessDefinition(); + if (!targetDefinition.equals(currentProcessDefinition)) { + LOGGER.debug("Migration plan found for {} does not match target definition {}, Found plan to {}.", processStateDefinition, currentProcessDefinition, targetDefinition); + return null; + } + + // target process not being deployed + if (!processes.processIds().contains(targetDefinition.getProcessId())) { + LOGGER.debug("No migration target defintion deployed in this container {} for migrating {}.", targetDefinition, processStateDefinition); + return null; + } + + // target process not matching version + org.kie.kogito.process.Process process = processes.processById(targetDefinition.getProcessId()); + ProcessDefinitionMigrationPlan targetDeployed = + new ProcessDefinitionMigrationPlan(process.id(), process.version()); + + return targetDeployed.equals(targetDefinition) ? plan : null; + } + + public boolean isEqualVersion(Processes processes, KogitoWorkflowProcessInstance processInstance) { + String currentProcessId = processInstance.getProcess().getId(); + String currentVersion = processInstance.getProcess().getVersion(); + ProcessDefinitionMigrationPlan currentProcessDefinition = new ProcessDefinitionMigrationPlan(currentProcessId, currentVersion); + RuleFlowProcessInstance pi = (RuleFlowProcessInstance) processInstance; - ProcessDefinitionMigrationPlan pd = - new ProcessDefinitionMigrationPlan(pi.getProcessId(), pi.getProcessVersion()); - return migrations.get(pd); + ProcessDefinitionMigrationPlan processStateDefinition = new ProcessDefinitionMigrationPlan(pi.getProcessId(), pi.getProcessVersion()); + + // check if definition and state match. we don't need to perform any migration. + return currentProcessDefinition.equals(processStateDefinition); } - public boolean shouldMigrate(KogitoWorkflowProcessInstance processInstance) { - return getMigrationPlan(processInstance) != null; + public boolean hasMigrationPlan(Processes processes, KogitoWorkflowProcessInstance processInstance) { + return getMigrationPlan(processes, processInstance) != null; } } diff --git a/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/impl/FileSystemMigrationPlanFileProvider.java b/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/impl/FileSystemMigrationPlanFileProvider.java index 7d26bc7f963..a4aacf1a2c6 100644 --- a/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/impl/FileSystemMigrationPlanFileProvider.java +++ b/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/migration/impl/FileSystemMigrationPlanFileProvider.java @@ -31,7 +31,9 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.jbpm.flow.migration.MigrationPlanFile; @@ -49,7 +51,7 @@ public class FileSystemMigrationPlanFileProvider implements MigrationPlanFilePro public static final String EXPLODED_MIGRATION_PLAN_FOLDER = "META-INF/migration-plan/"; public static final String MIGRATION_PLAN_FOLDER = "/META-INF/migration-plan/"; - private List rootPaths; + private Set rootPaths; public FileSystemMigrationPlanFileProvider() { try { @@ -57,7 +59,7 @@ public FileSystemMigrationPlanFileProvider() { if (url.isEmpty()) { url = Collections.list(JbpmClassLoaderUtil.findClassLoader().getResources(EXPLODED_MIGRATION_PLAN_FOLDER)); } - this.rootPaths = url.stream().map(this::toURI).filter(Optional::isPresent).map(Optional::get).toList(); + this.rootPaths = url.stream().map(this::toURI).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet()); } catch (IOException e) { throw new IllegalArgumentException("error trying to get Migration Plan folder"); } @@ -72,7 +74,7 @@ private Optional toURI(URL e) { } public FileSystemMigrationPlanFileProvider(URI rootPath) { - this.rootPaths = List.of(rootPath); + this.rootPaths = Set.of(rootPath); } @Override diff --git a/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/serialization/migration/StandardMigrationProcessInstanceMarshallerListener.java b/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/serialization/migration/StandardMigrationProcessInstanceMarshallerListener.java index 22ac0cdf039..b938110a994 100644 --- a/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/serialization/migration/StandardMigrationProcessInstanceMarshallerListener.java +++ b/jbpm/jbpm-flow-migration/src/main/java/org/jbpm/flow/serialization/migration/StandardMigrationProcessInstanceMarshallerListener.java @@ -20,9 +20,11 @@ import org.jbpm.flow.migration.MigrationPlanService; import org.jbpm.flow.serialization.ProcessInstanceMarshallerListener; +import org.jbpm.ruleflow.instance.RuleFlowProcessInstance; import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcessInstance; +import org.kie.kogito.process.Processes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,22 +43,28 @@ public StandardMigrationProcessInstanceMarshallerListener() { @SuppressWarnings("deprecation") @Override public void afterUnmarshallProcess(KogitoProcessRuntime runtime, KogitoWorkflowProcessInstance processInstance) { - if (!migrationPlanService.shouldMigrate(processInstance)) { + if (!migrationPlanService.hasMigrationPlan(runtime.getApplication().get(Processes.class), processInstance)) { + if (!this.migrationPlanService.isEqualVersion(runtime.getApplication().get(Processes.class), processInstance)) { + LOGGER.debug("Process State version and process container mismatch. Migrating process without plan."); + RuleFlowProcessInstance ruleFlowProcessInstance = (RuleFlowProcessInstance) processInstance; + ruleFlowProcessInstance.setProcess(ruleFlowProcessInstance.getProcess()); + } return; } - LOGGER.debug("Migration processInstance {}", processInstance); - migrationPlanService.migrateProcessElement(processInstance); + LOGGER.debug("Migration processInstance state {}-{} and definition {}-{}", + processInstance.getProcessId(), processInstance.getProcessVersion(), processInstance.getProcess().getId(), processInstance.getProcess().getVersion()); + migrationPlanService.migrateProcessElement(runtime.getApplication().get(Processes.class), processInstance); runtime.getProcessEventSupport().fireOnMigration(processInstance, runtime.getKieRuntime()); } @Override public void afterUnmarshallNode(KogitoProcessRuntime runtime, KogitoNodeInstance nodeInstance) { - if (!migrationPlanService.shouldMigrate((KogitoWorkflowProcessInstance) nodeInstance.getProcessInstance())) { + if (!migrationPlanService.hasMigrationPlan(runtime.getApplication().get(Processes.class), (KogitoWorkflowProcessInstance) nodeInstance.getProcessInstance())) { return; } LOGGER.debug("Migration nodeInstance {}", nodeInstance); - migrationPlanService.migrateNodeElement(nodeInstance); + migrationPlanService.migrateNodeElement(runtime.getApplication().get(Processes.class), nodeInstance); } } diff --git a/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanProviderTest.java b/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanProviderTest.java index 2a279c20f14..d417066b969 100644 --- a/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanProviderTest.java +++ b/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanProviderTest.java @@ -55,8 +55,8 @@ public void testReadingFiles() { plan.setProcessMigrationPlan(pdmp); assertThat(plans) - .hasSize(1) - .containsExactly(plan); + .hasSize(2) + .contains(plan); } diff --git a/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanServiceTest.java b/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanServiceTest.java index 1e2f141110e..2df1b83775f 100644 --- a/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanServiceTest.java +++ b/jbpm/jbpm-flow-migration/src/test/java/org/jbpm/flow/migration/MigrationPlanServiceTest.java @@ -18,23 +18,64 @@ */ package org.jbpm.flow.migration; +import java.util.Collections; + import org.jbpm.ruleflow.instance.RuleFlowProcessInstance; import org.jbpm.workflow.instance.impl.ExtendedNodeInstanceImpl; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.kie.kogito.process.Processes; +import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; import static org.jbpm.ruleflow.core.WorkflowElementIdentifierFactory.fromExternalFormat; +import static org.mockito.Mockito.when; +@TestInstance(Lifecycle.PER_CLASS) public class MigrationPlanServiceTest { + Processes processes; + org.kie.kogito.process.Process processB; + + @BeforeAll + public void init() { + processes = Mockito.mock(Processes.class); + when(processes.processIds()).thenReturn(Collections.singletonList("process_B")); + + processB = Mockito.mock(org.kie.kogito.process.Process.class); + when(processes.processById("process_B")).thenReturn(processB); + when(processB.id()).thenReturn("process_B"); + when(processB.version()).thenReturn("2"); + } + + @Test + public void testMigrationProcessInstanceSameProcessDefinition() { + MigrationPlanService service = new MigrationPlanService(); + + WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); + processImpl.setInternalProcess(new DummyProcess("process_A", "1")); + processImpl.setProcessId("process_A"); + processImpl.setProcessVersion("1"); + service.migrateProcessElement(processes, processImpl); + + assertThat(processImpl) + .hasFieldOrPropertyWithValue("processId", "process_A") + .hasFieldOrPropertyWithValue("processVersion", "1"); + + } + @Test public void testMigrationProcessInstance() { MigrationPlanService service = new MigrationPlanService(); WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); - processImpl.setProcess(new DummyProcess("process_A", "1")); - service.migrateProcessElement(processImpl); + processImpl.setInternalProcess(new DummyProcess("process_B", "2")); + processImpl.setProcessId("process_A"); + processImpl.setProcessVersion("1"); + service.migrateProcessElement(processes, processImpl); assertThat(processImpl) .hasFieldOrPropertyWithValue("processId", "process_B") @@ -42,12 +83,26 @@ public void testMigrationProcessInstance() { } + @Test + public void testMigrationProcessInstanceNotMatchingVersion() { + MigrationPlanService service = new MigrationPlanService(); + + WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); + processImpl.setProcess(new DummyProcess("process_D", "1")); + service.migrateProcessElement(processes, processImpl); + + assertThat(processImpl) + .hasFieldOrPropertyWithValue("processId", "process_D") + .hasFieldOrPropertyWithValue("processVersion", "1"); + + } + @Test public void testMigrationProcessNonExisting() { MigrationPlanService service = new MigrationPlanService(); WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); processImpl.setProcess(new DummyProcess("process_C", "1")); - service.migrateProcessElement(processImpl); + service.migrateProcessElement(processes, processImpl); assertThat(processImpl) .hasFieldOrPropertyWithValue("processId", "process_C") @@ -59,7 +114,7 @@ public void testMigrationProcessNotRightVersion() { MigrationPlanService service = new MigrationPlanService(); WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); processImpl.setProcess(new DummyProcess("process_A", "3")); - service.migrateProcessElement(processImpl); + service.migrateProcessElement(processes, processImpl); assertThat(processImpl) .hasFieldOrPropertyWithValue("processId", "process_A") @@ -70,12 +125,14 @@ public void testMigrationProcessNotRightVersion() { public void testMigrationNode() { MigrationPlanService service = new MigrationPlanService(); WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); - processImpl.setProcess(new DummyProcess("process_A", "1")); + processImpl.setInternalProcess(new DummyProcess("process_B", "2")); + processImpl.setProcessId("process_A"); + processImpl.setProcessVersion("1"); ExtendedNodeInstanceImpl nodeInstanceImpl = new ExtendedNodeInstanceImpl() { }; nodeInstanceImpl.setProcessInstance(processImpl); nodeInstanceImpl.setNodeId(fromExternalFormat("node_1")); - service.migrateNodeElement(nodeInstanceImpl); + service.migrateNodeElement(processes, nodeInstanceImpl); assertThat(nodeInstanceImpl) .hasFieldOrPropertyWithValue("nodeId", fromExternalFormat("node_2")); @@ -85,12 +142,14 @@ public void testMigrationNode() { public void testMigrationNodeNextItem() { MigrationPlanService service = new MigrationPlanService(); WorkflowProcessInstanceImpl processImpl = new RuleFlowProcessInstance(); - processImpl.setProcess(new DummyProcess("process_A", "1")); + processImpl.setInternalProcess(new DummyProcess("process_B", "2")); + processImpl.setProcessId("process_A"); + processImpl.setProcessVersion("1"); ExtendedNodeInstanceImpl nodeInstanceImpl = new ExtendedNodeInstanceImpl() { }; nodeInstanceImpl.setProcessInstance(processImpl); nodeInstanceImpl.setNodeId(fromExternalFormat("node_2")); - service.migrateNodeElement(nodeInstanceImpl); + service.migrateNodeElement(processes, nodeInstanceImpl); assertThat(nodeInstanceImpl) .hasFieldOrPropertyWithValue("nodeId", fromExternalFormat("node_3")); @@ -105,7 +164,7 @@ public void testMigrationNodeNonExistent() { }; nodeInstanceImpl.setProcessInstance(processImpl); nodeInstanceImpl.setNodeId(fromExternalFormat("node_3")); - service.migrateNodeElement(nodeInstanceImpl); + service.migrateNodeElement(processes, nodeInstanceImpl); assertThat(nodeInstanceImpl) .hasFieldOrPropertyWithValue("nodeId", fromExternalFormat("node_3")); @@ -120,7 +179,7 @@ public void testMigrationNodeNotMigratedWrongProcess() { }; nodeInstanceImpl.setProcessInstance(processImpl); nodeInstanceImpl.setNodeId(fromExternalFormat("node_3")); - service.migrateNodeElement(nodeInstanceImpl); + service.migrateNodeElement(processes, nodeInstanceImpl); assertThat(nodeInstanceImpl) .hasFieldOrPropertyWithValue("nodeId", fromExternalFormat("node_3")); diff --git a/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration.mpf b/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration.mpf index 98d93052b2e..9daed6fad12 100644 --- a/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration.mpf +++ b/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration.mpf @@ -1,13 +1,13 @@ { - "name" : "my simple migration", + "name" : "my simple migration versioned", "processMigrationPlan" : { "sourceProcessDefinition" : { - "processId" : "process_A", + "processId" : "process_D", "processVersion" : "1" }, "targetProcessDefinition" : { "processId" : "process_B", - "processVersion" : "2" + "processVersion" : "3" }, "nodeInstanceMigrationPlan" : [ { diff --git a/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration_version.mpf b/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration_version.mpf new file mode 100644 index 00000000000..98d93052b2e --- /dev/null +++ b/jbpm/jbpm-flow-migration/src/test/resources/META-INF/migration-plan/simple_migration_version.mpf @@ -0,0 +1,23 @@ +{ + "name" : "my simple migration", + "processMigrationPlan" : { + "sourceProcessDefinition" : { + "processId" : "process_A", + "processVersion" : "1" + }, + "targetProcessDefinition" : { + "processId" : "process_B", + "processVersion" : "2" + }, + "nodeInstanceMigrationPlan" : [ + { + "sourceNodeId" : "node_1", + "targetNodeId" : "node_2" + }, + { + "sourceNodeId" : "node_2", + "targetNodeId" : "node_3" + } + ] + } +} diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java index 6ab030d9ffc..e23c5b4bc82 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java @@ -257,7 +257,6 @@ public final void trigger(KogitoNodeInstance from, String type) { } protected void captureError(Exception e) { - e.printStackTrace(); logger.error("capture error", e); getProcessInstance().setErrorState(this, e); } diff --git a/kogito-bom/pom.xml b/kogito-bom/pom.xml index 61ce872c187..b07eec3f819 100755 --- a/kogito-bom/pom.xml +++ b/kogito-bom/pom.xml @@ -1941,6 +1941,28 @@ ${project.version} sources + + org.kie.kogito + kogito-serverless-workflow-dmn-parser + ${project.version} + + + org.kie.kogito + kogito-serverless-workflow-dmn-parser + ${project.version} + sources + + + org.kie.kogito + kogito-serverless-workflow-dmn + ${project.version} + + + org.kie.kogito + kogito-serverless-workflow-dmn + ${project.version} + sources + org.kie.kogito kogito-serverless-workflow-rest-parser @@ -2301,52 +2323,6 @@ ${project.version} - - - org.jbpm - jbpm-quarkus-devui - ${project.version} - - - org.jbpm - jbpm-quarkus-devui - ${project.version} - sources - - - org.jbpm - jbpm-quarkus-devui-deployment - ${project.version} - - - org.jbpm - jbpm-quarkus-devui-deployment - ${project.version} - sources - - - org.apache.kie.sonataflow - sonataflow-quarkus-devui - ${project.version} - - - org.apache.kie.sonataflow - sonataflow-quarkus-devui - ${project.version} - sources - - - org.apache.kie.sonataflow - sonataflow-quarkus-devui-deployment - ${project.version} - - - org.apache.kie.sonataflow - sonataflow-quarkus-devui-deployment - ${project.version} - sources - - org.kie diff --git a/kogito-codegen-modules/kogito-codegen-api/src/main/java/org/kie/kogito/codegen/api/context/impl/AbstractKogitoBuildContext.java b/kogito-codegen-modules/kogito-codegen-api/src/main/java/org/kie/kogito/codegen/api/context/impl/AbstractKogitoBuildContext.java index 25a0e8794da..d46cfdb118a 100644 --- a/kogito-codegen-modules/kogito-codegen-api/src/main/java/org/kie/kogito/codegen/api/context/impl/AbstractKogitoBuildContext.java +++ b/kogito-codegen-modules/kogito-codegen-api/src/main/java/org/kie/kogito/codegen/api/context/impl/AbstractKogitoBuildContext.java @@ -21,7 +21,6 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.nio.file.Path; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; @@ -258,7 +257,7 @@ protected abstract static class AbstractBuilder implements Builder { protected Predicate classAvailabilityResolver = this::hasClass; protected Predicate> classSubTypeAvailabilityResolver = c -> false; // default fallback value (usually overridden) - protected AppPaths appPaths = AppPaths.fromProjectDir(new File(".").toPath(), Path.of(".", AppPaths.TARGET_DIR)); + protected AppPaths appPaths = AppPaths.fromProjectDir(new File(".").toPath()); protected KogitoGAV gav; protected AbstractBuilder() { diff --git a/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/utils/GeneratedFileWriter.java b/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/utils/GeneratedFileWriter.java deleted file mode 100644 index d596702592a..00000000000 --- a/kogito-codegen-modules/kogito-codegen-core/src/main/java/org/kie/kogito/codegen/core/utils/GeneratedFileWriter.java +++ /dev/null @@ -1,153 +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.kogito.codegen.core.utils; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; - -import org.drools.codegen.common.GeneratedFile; -import org.drools.codegen.common.GeneratedFileType; - -/** - * Writes {@link GeneratedFile} to the right directory, depending on its - * {@link GeneratedFileType.Category} - */ -public class GeneratedFileWriter { - - public static class Builder { - - private final String classesDir; - private final String sourcesDir; - private final String resourcePath; - private final String scaffoldedSourcesDir; - - /** - * - * @param classesDir usually target/classes/ - * @param sourcesDir usually target/generated-sources/kogito/ - * @param resourcesDir usually target/generated-resources/kogito/ - * @param scaffoldedSourcesDir usually src/main/java/ - */ - public Builder(String classesDir, String sourcesDir, String resourcesDir, String scaffoldedSourcesDir) { - this.classesDir = classesDir; - this.sourcesDir = sourcesDir; - this.resourcePath = resourcesDir; - this.scaffoldedSourcesDir = scaffoldedSourcesDir; - } - - /** - * @param basePath the path to which the given subdirectories will be written - * e.g. ${basePath}/${classesDir}/myfile.ext - * - */ - public GeneratedFileWriter build(Path basePath) { - return new GeneratedFileWriter( - basePath.resolve(classesDir), - basePath.resolve(sourcesDir), - basePath.resolve(resourcePath), - basePath.resolve(scaffoldedSourcesDir)); - } - } - - private final Path classesDir; - private final Path sourcesDir; - private final Path resourcePath; - private final Path scaffoldedSourcesDir; - - public static final String DEFAULT_SOURCES_DIR = "generated-sources/kogito/"; - public static final String DEFAULT_RESOURCE_PATH = "generated-resources/kogito/"; - public static final String DEFAULT_SCAFFOLDED_SOURCES_DIR = "src/main/java/"; - public static final String DEFAULT_CLASSES_DIR = "target/classes"; - - /** - * - * @param classesDir usually {@link #DEFAULT_CLASSES_DIR} - * @param sourcesDir usually target/generated-sources/kogito/. See {@link #DEFAULT_SOURCES_DIR} - * @param resourcePath usually target/generated-resources/kogito/ {@link #DEFAULT_RESOURCE_PATH} - * @param scaffoldedSourcesDir usually {@link #DEFAULT_SCAFFOLDED_SOURCES_DIR} - */ - public GeneratedFileWriter(Path classesDir, Path sourcesDir, Path resourcePath, Path scaffoldedSourcesDir) { - this.classesDir = classesDir; - this.sourcesDir = sourcesDir; - this.resourcePath = resourcePath; - this.scaffoldedSourcesDir = scaffoldedSourcesDir; - } - - public void writeAll(Collection generatedFiles) { - generatedFiles.forEach(this::write); - } - - public void write(GeneratedFile f) throws UncheckedIOException { - try { - GeneratedFileType.Category category = f.category(); - switch (category) { - case INTERNAL_RESOURCE: // since codegen happens after maven-resource-plugin (both in Quarkus and SB), need to manually place in the correct (CP) location - case STATIC_HTTP_RESOURCE: - case COMPILED_CLASS: - writeGeneratedFile(f, classesDir); - break; - case SOURCE: - if (f.type().isCustomizable()) { - writeGeneratedFile(f, scaffoldedSourcesDir); - } else { - writeGeneratedFile(f, sourcesDir); - } - break; - default: - throw new IllegalArgumentException("Unknown Category " + category.name()); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public Path getClassesDir() { - return classesDir; - } - - public Path getSourcesDir() { - return sourcesDir; - } - - public Path getResourcePath() { - return resourcePath; - } - - public Path getScaffoldedSourcesDir() { - return scaffoldedSourcesDir; - } - - private void writeGeneratedFile(GeneratedFile f, Path location) throws IOException { - if (location == null) { - return; - } - Files.write( - pathOf(location, f.path()), - f.contents()); - } - - private Path pathOf(Path location, Path end) throws IOException { - Path path = location.resolve(end); - Files.createDirectories(path.getParent()); - return path; - } -} diff --git a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PMMLRestResourceGeneratorTest.java b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PMMLRestResourceGeneratorTest.java index 8fe6c0dd94b..b69a9aa614e 100644 --- a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PMMLRestResourceGeneratorTest.java +++ b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PMMLRestResourceGeneratorTest.java @@ -22,6 +22,7 @@ import java.util.NoSuchElementException; import java.util.Optional; +import org.drools.codegen.common.AppPaths; import org.drools.codegen.common.di.impl.CDIDependencyInjectionAnnotator; import org.drools.util.StringUtils; import org.junit.jupiter.api.AfterAll; @@ -80,7 +81,7 @@ public static void setup() { String filePrefix = URLEncoder.encode(getSanitizedClassName(KIE_PMML_MODEL.getFileName().replace(".pmml", ""))); String classPrefix = URLEncoder.encode(getSanitizedClassName(KIE_PMML_MODEL.getName())); expectedUrl = String.format("/%s/%s", filePrefix, classPrefix); - System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, "target/test-classes"); + System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, String.format("%s/test-classes", AppPaths.TARGET_DIR)); } @AfterAll diff --git a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenFactoryTest.java b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenFactoryTest.java index 1c14e042a6f..d378b501f70 100644 --- a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenFactoryTest.java +++ b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenFactoryTest.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; +import org.drools.codegen.common.AppPaths; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; @@ -46,7 +47,7 @@ class PredictionCodegenFactoryTest { @BeforeAll public static void setup() { - System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, "target/test-classes"); + System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, String.format("%s/test-classes", AppPaths.TARGET_DIR)); } @AfterAll diff --git a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenGenerateTest.java b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenGenerateTest.java index b26c7bbef86..c563288e284 100644 --- a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenGenerateTest.java +++ b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenGenerateTest.java @@ -29,6 +29,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.drools.codegen.common.AppPaths; import org.drools.codegen.common.GeneratedFile; import org.drools.codegen.common.GeneratedFileType; import org.junit.jupiter.api.AfterAll; @@ -68,7 +69,7 @@ class PredictionCodegenGenerateTest { @BeforeAll public static void setup() { - System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, "target/test-classes"); + System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, String.format("%s/test-classes", AppPaths.TARGET_DIR)); } @AfterAll diff --git a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenInternalGenerateTest.java b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenInternalGenerateTest.java index 27b91deb5b7..6f6229c4dee 100644 --- a/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenInternalGenerateTest.java +++ b/kogito-codegen-modules/kogito-codegen-predictions/src/test/java/org/kie/kogito/codegen/prediction/PredictionCodegenInternalGenerateTest.java @@ -25,6 +25,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.drools.codegen.common.AppPaths; import org.drools.codegen.common.GeneratedFile; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -51,7 +52,7 @@ class PredictionCodegenInternalGenerateTest { @BeforeAll public static void setup() { - System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, "target/test-classes"); + System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, String.format("%s/test-classes", AppPaths.TARGET_DIR)); } @AfterAll diff --git a/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/AbstractKieMojo.java b/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/AbstractKieMojo.java index 81eb6a6c262..b279c5eafc1 100644 --- a/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/AbstractKieMojo.java +++ b/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/AbstractKieMojo.java @@ -22,6 +22,7 @@ import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; import java.util.Collection; import java.util.Iterator; import java.util.Map; @@ -37,13 +38,13 @@ import org.drools.codegen.common.AppPaths; import org.drools.codegen.common.DroolsModelBuildContext; import org.drools.codegen.common.GeneratedFile; +import org.drools.codegen.common.GeneratedFileWriter; import org.kie.kogito.KogitoGAV; import org.kie.kogito.codegen.api.Generator; import org.kie.kogito.codegen.api.context.KogitoBuildContext; import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; import org.kie.kogito.codegen.api.context.impl.QuarkusKogitoBuildContext; import org.kie.kogito.codegen.api.context.impl.SpringBootKogitoBuildContext; -import org.kie.kogito.codegen.core.utils.GeneratedFileWriter; import org.kie.kogito.codegen.decision.DecisionCodegen; import org.kie.kogito.codegen.prediction.PredictionCodegen; import org.kie.kogito.codegen.process.ProcessCodegen; @@ -55,6 +56,8 @@ public abstract class AbstractKieMojo extends AbstractMojo { + protected static final GeneratedFileWriter.Builder generatedFileWriterBuilder = GeneratedFileWriter.builder("kogito", "kogito.codegen.resources.directory", "kogito.codegen.sources.directory"); + @Parameter(required = true, defaultValue = "${project.basedir}") protected File projectDir; @@ -67,11 +70,8 @@ public abstract class AbstractKieMojo extends AbstractMojo { @Parameter(required = true, defaultValue = "${project.build.outputDirectory}") protected File outputDirectory; - @Parameter(defaultValue = "${project.build.directory}/" + GeneratedFileWriter.DEFAULT_SOURCES_DIR) - protected File generatedSources; - - @Parameter(defaultValue = "${project.build.directory}/" + GeneratedFileWriter.DEFAULT_RESOURCE_PATH) - protected File generatedResources; + @Parameter(required = true, defaultValue = "${project.basedir}") + protected File baseDir; @Parameter(property = "kogito.codegen.persistence", defaultValue = "true") protected boolean persistence; @@ -102,7 +102,7 @@ protected void setSystemProperties(Map properties) { } protected KogitoBuildContext discoverKogitoRuntimeContext(ClassLoader classLoader) { - AppPaths appPaths = AppPaths.fromProjectDir(projectDir.toPath(), outputDirectory.toPath()); + AppPaths appPaths = AppPaths.fromProjectDir(projectDir.toPath()); KogitoBuildContext context = contextBuilder() .withClassAvailabilityResolver(this::hasClassOnClasspath) .withClassSubTypeAvailabilityResolver(classSubTypeAvailabilityResolver()) @@ -250,20 +250,27 @@ private boolean hasClassOnClasspath(String className) { } protected void writeGeneratedFiles(Collection generatedFiles) { - generatedFiles.forEach(this::writeGeneratedFile); + GeneratedFileWriter writer = getGeneratedFileWriter(); + generatedFiles.forEach(generatedFile -> writeGeneratedFile(generatedFile, writer)); } protected void writeGeneratedFile(GeneratedFile generatedFile) { - GeneratedFileWriter writer = new GeneratedFileWriter(outputDirectory.toPath(), - generatedSources.toPath(), - generatedResources.toPath(), - getSourcesPath().toPath()); + writeGeneratedFile(generatedFile, getGeneratedFileWriter()); + } + protected void writeGeneratedFile(GeneratedFile generatedFile, GeneratedFileWriter writer) { getLog().info("Generating: " + generatedFile.relativePath()); writer.write(generatedFile); } protected File getSourcesPath() { - return generatedSources; + // using runtime BT instead of static AppPaths.MAVEN to allow + // invocation from GRADLE + return Path.of(baseDir.getAbsolutePath(), AppPaths.BT.GENERATED_SOURCES_PATH.toString()).toFile(); + } + + protected GeneratedFileWriter getGeneratedFileWriter() { + return generatedFileWriterBuilder + .build(Path.of(baseDir.getAbsolutePath())); } } diff --git a/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/GenerateModelMojo.java b/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/GenerateModelMojo.java index 9b101d390d4..b1c3c675102 100644 --- a/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/GenerateModelMojo.java +++ b/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/GenerateModelMojo.java @@ -54,9 +54,6 @@ public class GenerateModelMojo extends AbstractKieMojo { public static final PathMatcher drlFileMatcher = FileSystems.getDefault().getPathMatcher("glob:**.drl"); - @Parameter(property = "kogito.codegen.sources.directory", defaultValue = "${project.build.directory}/generated-sources/kogito") - private File customizableSourcesPath; - /** * Partial generation can be used when reprocessing a pre-compiled project * for faster code-generation. It only generates code for rules and processes, @@ -72,19 +69,16 @@ public class GenerateModelMojo extends AbstractKieMojo { @Parameter(property = "kogito.sources.keep", defaultValue = "false") private boolean keepSources; - @Parameter(property = "build.output.directory", readonly = true, defaultValue = "${project.build.directory}/classes/") - private String buildOutputDirectory; - @Override public void execute() throws MojoExecutionException { // TODO to be removed with DROOLS-7090 boolean indexFileDirectorySet = false; - getLog().debug("execute -> " + buildOutputDirectory); - if (buildOutputDirectory == null) { + getLog().debug("execute -> " + outputDirectory); + if (outputDirectory == null) { throw new MojoExecutionException("${project.build.directory} is null"); } if (System.getProperty(INDEXFILE_DIRECTORY_PROPERTY) == null) { - System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, buildOutputDirectory); + System.setProperty(INDEXFILE_DIRECTORY_PROPERTY, outputDirectory.toString()); indexFileDirectorySet = true; } addCompileSourceRoots(); @@ -103,14 +97,8 @@ protected boolean isOnDemand() { return onDemand; } - @Override - protected File getSourcesPath() { - return customizableSourcesPath; - } - protected void addCompileSourceRoots() { - project.addCompileSourceRoot(getSourcesPath().getPath()); - project.addCompileSourceRoot(generatedSources.getPath()); + project.addCompileSourceRoot(getGeneratedFileWriter().getScaffoldedSourcesDir().toString()); } protected void generateModel() throws MojoExecutionException { @@ -129,9 +117,11 @@ protected void generateModel() throws MojoExecutionException { Map> mappedGeneratedFiles = generatedFiles.stream() .collect(Collectors.groupingBy(GeneratedFile::type)); - mappedGeneratedFiles.entrySet().stream() + List generatedUncompiledFiles = mappedGeneratedFiles.entrySet().stream() .filter(entry -> !entry.getKey().equals(COMPILED_CLASS)) - .forEach(entry -> writeGeneratedFiles(entry.getValue())); + .flatMap(entry -> entry.getValue().stream()) + .toList(); + writeGeneratedFiles(generatedUncompiledFiles); List generatedCompiledFiles = mappedGeneratedFiles.getOrDefault(COMPILED_CLASS, Collections.emptyList()) diff --git a/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/ProcessClassesMojo.java b/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/ProcessClassesMojo.java index f5ffd9b3d85..ef491c7eca3 100644 --- a/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/ProcessClassesMojo.java +++ b/kogito-maven-plugin/src/main/java/org/kie/kogito/maven/plugin/ProcessClassesMojo.java @@ -98,14 +98,14 @@ public void execute() throws MojoExecutionException { compileAndWriteClasses(generatedClasses, classLoader, settings); // Dump resources - generatedResources.forEach(this::writeGeneratedFile); + this.writeGeneratedFiles(generatedResources); // Json schema generation Stream> processClassStream = getReflections().getTypesAnnotatedWith(ProcessInput.class).stream(); - generateJsonSchema(processClassStream).forEach(this::writeGeneratedFile); + writeGeneratedFiles(generateJsonSchema(processClassStream)); Stream> userTaskClassStream = getReflections().getTypesAnnotatedWith(UserTask.class).stream(); - generateJsonSchema(userTaskClassStream).forEach(this::writeGeneratedFile); + writeGeneratedFiles(generateJsonSchema(userTaskClassStream)); } catch (Exception e) { throw new MojoExecutionException("Error during processing model classes", e); } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java index 56b80a8beba..413c199a674 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java @@ -24,6 +24,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Optional; @@ -40,7 +42,7 @@ public class ClassPathContentLoader extends CachedContentLoader { static String getPath(URI uri) { final String classPathPrefix = "classpath:"; - String str = uri.toString(); + String str = URLDecoder.decode(uri.toString(), Charset.defaultCharset()); if (str.toLowerCase().startsWith(classPathPrefix)) { str = str.substring(classPathPrefix.length()); while (str.startsWith("/")) { diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java index 002174e876a..6e5b806594a 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java @@ -23,6 +23,8 @@ import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.Charset; import java.util.Optional; import org.kie.kogito.serverless.workflow.parser.ParserContext; @@ -91,7 +93,7 @@ public static Builder builder(URI uri) { } public static Builder builder(String uri) { - return new Builder(URI.create(uri)); + return new Builder(URI.create(URLEncoder.encode(uri, Charset.defaultCharset()))); } public static class Builder { diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingSetter.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingSetter.java new file mode 100644 index 00000000000..adfa6a4e627 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingSetter.java @@ -0,0 +1,26 @@ +/* + * 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.kogito.serverless.workflow.parser.handlers; + +public interface MappingSetter { + + void accept(Object value); + + void accept(String key, Object value); +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingUtils.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingUtils.java new file mode 100644 index 00000000000..ec644248312 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/MappingUtils.java @@ -0,0 +1,74 @@ +/* + * 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.kogito.serverless.workflow.parser.handlers; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.jbpm.ruleflow.core.factory.MappableNodeFactory; +import org.kie.kogito.jackson.utils.JsonNodeVisitor; +import org.kie.kogito.jackson.utils.JsonObjectUtils; +import org.kie.kogito.serverless.workflow.SWFConstants; +import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; + +import com.fasterxml.jackson.databind.JsonNode; + +import io.serverlessworkflow.api.Workflow; + +public class MappingUtils { + + public static > T addMapping(T nodeFactory, String inputVar, String outputVar) { + return (T) nodeFactory.inMapping(inputVar, SWFConstants.MODEL_WORKFLOW_VAR) + .outMapping(SWFConstants.RESULT, outputVar); + } + + public static final void processArgs(Workflow workflow, + JsonNode functionArgs, MappingSetter setter) { + if (functionArgs.isObject()) { + functionsToMap(workflow, functionArgs).forEach((key, value) -> setter.accept(key, value)); + } else { + Object object = functionReference(workflow, JsonObjectUtils.simpleToJavaValue(functionArgs)); + setter.accept(object); + } + } + + private static Map functionsToMap(Workflow workflow, JsonNode jsonNode) { + Map map = new LinkedHashMap<>(); + if (jsonNode != null) { + Iterator> iter = jsonNode.fields(); + while (iter.hasNext()) { + Entry entry = iter.next(); + map.put(entry.getKey(), functionReference(workflow, JsonObjectUtils.simpleToJavaValue(entry.getValue()))); + } + } + return map; + } + + private static Object functionReference(Workflow workflow, Object object) { + if (object instanceof JsonNode) { + return JsonNodeVisitor.transformTextNode((JsonNode) object, node -> JsonObjectUtils.fromValue(ExpressionHandlerUtils.replaceExpr(workflow, node.asText()))); + } else if (object instanceof CharSequence) { + return ExpressionHandlerUtils.replaceExpr(workflow, object.toString()); + } else { + return object; + } + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java index bd7b522b47c..fbb03806de0 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/WorkItemBuilder.java @@ -19,20 +19,17 @@ package org.kie.kogito.serverless.workflow.utils; import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Map.Entry; import org.jbpm.process.core.datatype.DataType; import org.jbpm.process.core.datatype.DataTypeResolver; import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory; import org.jbpm.ruleflow.core.factory.WorkItemNodeFactory; -import org.kie.kogito.jackson.utils.JsonNodeVisitor; -import org.kie.kogito.jackson.utils.JsonObjectUtils; import org.kie.kogito.process.expr.ExpressionHandlerFactory; import org.kie.kogito.serverless.workflow.SWFConstants; import org.kie.kogito.serverless.workflow.parser.ParserContext; +import org.kie.kogito.serverless.workflow.parser.handlers.MappingSetter; +import org.kie.kogito.serverless.workflow.parser.handlers.MappingUtils; import org.kie.kogito.serverless.workflow.suppliers.ExpressionParametersFactorySupplier; import org.kie.kogito.serverless.workflow.suppliers.ObjectResolverSupplier; @@ -45,8 +42,6 @@ public abstract class WorkItemBuilder { - private static final String RESULT = "Result"; - protected > WorkItemNodeFactory addFunctionArgs(Workflow workflow, WorkItemNodeFactory node, FunctionRef functionRef) { JsonNode functionArgs = functionRef.getArguments(); if (functionArgs != null) { @@ -72,59 +67,40 @@ protected WorkItemNodeFactory buildWorkItem(RuleFlowNodeContainerFactory workItemFactory, JsonNode functionArgs, String paramName) { - if (functionArgs.isObject()) { - functionsToMap(workflow, functionArgs).forEach((key, value) -> processArg(workflow, key, value, workItemFactory, paramName)); - } else { - Object object = functionReference(workflow, JsonObjectUtils.simpleToJavaValue(functionArgs)); - boolean isExpr = isExpression(workflow, object); - if (isExpr) { - workItemFactory.workParameterFactory(new ExpressionParametersFactorySupplier(workflow.getExpressionLang(), object, paramName)); - } else { - workItemFactory.workParameter(SWFConstants.CONTENT_DATA, object); + MappingUtils.processArgs(workflow, functionArgs, new MappingSetter() { + @Override + public void accept(String key, Object value) { + boolean isExpr = isExpression(workflow, value); + workItemFactory + .workParameter(key, + isExpr ? new ObjectResolverSupplier(workflow.getExpressionLang(), value, paramName) : value) + .workParameterDefinition(key, + getDataType(value, isExpr)); } - workItemFactory.workParameterDefinition(SWFConstants.CONTENT_DATA, getDataType(object, isExpr)); - } - } - private Map functionsToMap(Workflow workflow, JsonNode jsonNode) { - Map map = new LinkedHashMap<>(); - if (jsonNode != null) { - Iterator> iter = jsonNode.fields(); - while (iter.hasNext()) { - Entry entry = iter.next(); - map.put(entry.getKey(), functionReference(workflow, JsonObjectUtils.simpleToJavaValue(entry.getValue()))); + @Override + public void accept(Object value) { + boolean isExpr = isExpression(workflow, value); + if (isExpr) { + workItemFactory.workParameterFactory(new ExpressionParametersFactorySupplier(workflow.getExpressionLang(), value, paramName)); + } else { + workItemFactory.workParameter(SWFConstants.CONTENT_DATA, value); + } + workItemFactory.workParameterDefinition(SWFConstants.CONTENT_DATA, getDataType(value, isExpr)); } - } - return map; + }); } - private Object functionReference(Workflow workflow, Object object) { - if (object instanceof JsonNode) { - return JsonNodeVisitor.transformTextNode((JsonNode) object, node -> JsonObjectUtils.fromValue(ExpressionHandlerUtils.replaceExpr(workflow, node.asText()))); - } else if (object instanceof CharSequence) { - return ExpressionHandlerUtils.replaceExpr(workflow, object.toString()); - } else { - return object; - } - } - - private void processArg(Workflow workflow, String key, Object value, WorkItemNodeFactory workItemFactory, String paramName) { - boolean isExpr = isExpression(workflow, value); - workItemFactory - .workParameter(key, - isExpr ? new ObjectResolverSupplier(workflow.getExpressionLang(), value, paramName) : value) - .workParameterDefinition(key, - getDataType(value, isExpr)); + private static boolean isExpression(Workflow workflow, Object value) { + return value instanceof CharSequence && ExpressionHandlerFactory.get(workflow.getExpressionLang(), value.toString()).isValid() || value instanceof JsonNode; } - DataType getDataType(Object object, boolean isExpr) { + private static DataType getDataType(Object object, boolean isExpr) { if (object instanceof ObjectNode) { return DataTypeResolver.fromClass(Map.class); } else if (object instanceof ArrayNode) { @@ -133,8 +109,4 @@ DataType getDataType(Object object, boolean isExpr) { return DataTypeResolver.fromObject(object, isExpr); } } - - private boolean isExpression(Workflow workflow, Object value) { - return value instanceof CharSequence && ExpressionHandlerFactory.get(workflow.getExpressionLang(), value.toString()).isValid() || value instanceof JsonNode; - } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java index d3d9ac9b3d6..e3c2ec36962 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java @@ -27,7 +27,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.builder; import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.compoundURI; import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readString; @@ -53,7 +52,7 @@ void testNotExistingFile() { @Test void testNotExistingClasspath() { Builder builder = builder("classpath:/noPepe.txt"); - assertThatIllegalArgumentException().isThrownBy(() -> readString(builder)); + assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> readString(builder)); } @Test diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/pom.xml b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/pom.xml new file mode 100644 index 00000000000..cbb756c105a --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/pom.xml @@ -0,0 +1,62 @@ + + 4.0.0 + + org.kie.kogito + kogito-serverless-workflow + 999-SNAPSHOT + + Kogito :: Serverless Workflow :: DMN :: Parser + + + org.kie.kogito.serverless.workflow.dmn.parser + + + kogito-serverless-workflow-dmn-parser + + + org.kie.kogito + kogito-serverless-workflow-builder + + + org.kie.kogito + kogito-serverless-workflow-dmn + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + ch.qos.logback + logback-classic + test + + + org.kie.kogito + kogito-serverless-workflow-fluent + test + + + org.kie.kogito + kogito-serverless-workflow-executor-core + test + + + org.kie + kie-dmn-test-resources + tests + ${project.version} + test + + + org.assertj + assertj-core + test + + + \ No newline at end of file diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/types/DMNTypeHandler.java b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/types/DMNTypeHandler.java new file mode 100644 index 00000000000..0b98f50dc24 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/types/DMNTypeHandler.java @@ -0,0 +1,103 @@ +/* + * 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.kogito.serverless.workflow.parser.types; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.Objects; + +import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory; +import org.jbpm.ruleflow.core.factory.NodeFactory; +import org.jbpm.ruleflow.core.factory.RuleSetNodeFactory; +import org.kie.kogito.decision.DecisionModel; +import org.kie.kogito.dmn.DMNKogito; +import org.kie.kogito.dmn.DmnDecisionModel; +import org.kie.kogito.serverless.workflow.SWFConstants; +import org.kie.kogito.serverless.workflow.dmn.SWFDecisionEngine; +import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory; +import org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler; +import org.kie.kogito.serverless.workflow.parser.ParserContext; +import org.kie.kogito.serverless.workflow.parser.VariableInfo; +import org.kie.kogito.serverless.workflow.parser.handlers.MappingSetter; +import org.kie.kogito.serverless.workflow.parser.handlers.MappingUtils; + +import com.fasterxml.jackson.databind.JsonNode; + +import io.serverlessworkflow.api.Workflow; +import io.serverlessworkflow.api.functions.FunctionDefinition; +import io.serverlessworkflow.api.functions.FunctionRef; + +public class DMNTypeHandler implements FunctionTypeHandler { + + private static final String DMN_TYPE = "dmn"; + public static final String NAMESPACE = "namespace"; + public static final String MODEL = "model"; + public static final String FILE = "file"; + + private static final String REQUIRED_MESSAGE = "%s is required on metadata for DMN"; + + @Override + public String type() { + return DMN_TYPE; + } + + @Override + public boolean isCustom() { + return true; + } + + @Override + public NodeFactory getActionNode(Workflow workflow, ParserContext context, + RuleFlowNodeContainerFactory embeddedSubProcess, FunctionDefinition functionDef, + FunctionRef functionRef, VariableInfo varInfo) { + Map metadata = Objects.requireNonNull(functionDef.getMetadata(), "Metadata is required for DMN"); + String namespace = Objects.requireNonNull(metadata.get(NAMESPACE), String.format(REQUIRED_MESSAGE, NAMESPACE)); + String model = Objects.requireNonNull(metadata.get(MODEL), String.format(REQUIRED_MESSAGE, MODEL)); + String file = Objects.requireNonNull(metadata.get(FILE), String.format(REQUIRED_MESSAGE, FILE)); + RuleSetNodeFactory nodeFactory = MappingUtils.addMapping(embeddedSubProcess.ruleSetNode(context.newId()).decision(namespace, model, model, () -> loadDMNFromFile(namespace, model, file)), + varInfo.getInputVar(), varInfo.getOutputVar()); + JsonNode functionArgs = functionRef.getArguments(); + if (functionArgs != null) { + nodeFactory.metaData(SWFDecisionEngine.EXPR_LANG, workflow.getExpressionLang()); + MappingUtils.processArgs(workflow, functionArgs, new MappingSetter() { + @Override + public void accept(String key, Object value) { + nodeFactory.parameter(key, value); + } + + @Override + public void accept(Object value) { + nodeFactory.parameter(SWFConstants.CONTENT_DATA, value); + } + }); + } + return nodeFactory; + } + + private DecisionModel loadDMNFromFile(String namespace, String model, String file) { + try (Reader reader = new InputStreamReader(URIContentLoaderFactory.builder(file).withClassloader(this.getClass().getClassLoader()).build().getInputStream())) { + return new DmnDecisionModel(DMNKogito.createGenericDMNRuntime(reader), namespace, model); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/resources/META-INF/services/org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/resources/META-INF/services/org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler new file mode 100644 index 00000000000..8169c5bf60a --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/main/resources/META-INF/services/org.kie.kogito.serverless.workflow.parser.FunctionTypeHandler @@ -0,0 +1 @@ +org.kie.kogito.serverless.workflow.parser.types.DMNTypeHandler diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/test/java/org/kie/kogito/serverless/workflow/dmn/SWFDMNTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/test/java/org/kie/kogito/serverless/workflow/dmn/SWFDMNTest.java new file mode 100644 index 00000000000..d2828b98930 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn-parser/src/test/java/org/kie/kogito/serverless/workflow/dmn/SWFDMNTest.java @@ -0,0 +1,57 @@ +package org.kie.kogito.serverless.workflow.dmn; + +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.kie.kogito.serverless.workflow.parser.types.DMNTypeHandler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; + +import io.serverlessworkflow.api.Workflow; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call; +import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.custom; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; + +public class SWFDMNTest { + @Test + void testDMNFile() throws IOException { + doIt(buildWorkflow(Collections.emptyMap())); + } + + @Test + void testDMNFileWithArgs() throws IOException { + doIt(buildWorkflow(Map.of("Driver", ".Driver", "Violation", ".Violation"))); + } + + @Test + void testDMNFileWithExprArg() throws IOException { + doIt(buildWorkflow("{Driver:.Driver,Violation:.Violation}")); + } + + private void doIt(Workflow workflow) { + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + JsonNode response = application.execute(workflow, Map.of("Driver", Map.of("Name", "Pepe", "Age", 19, "Points", 0, "State", "Spain", "City", "Zaragoza"), "Violation", Map.of("Code", "12", + "Date", new Date(System.currentTimeMillis()), "Type", "parking"))).getWorkflowdata(); + assertThat(response.get("Should the driver be suspended?")).isEqualTo(new TextNode("No")); + response = application.execute(workflow, Map.of("Driver", Map.of("Name", "Pepe", "Age", 19, "Points", 19, "State", "Spain", "City", "Zaragoza"), "Violation", Map.of("Code", "12", + "Date", new Date(System.currentTimeMillis()), "Type", "speed", "Speed Limit", "120", "Actual Speed", "180"))).getWorkflowdata(); + assertThat(response.get("Should the driver be suspended?")).isEqualTo(new TextNode("Yes")); + } + } + + private Workflow buildWorkflow(Object args) { + return workflow("PlayingWithDMN") + .start(operation().action(call(custom("DMNTest", "dmn").metadata(DMNTypeHandler.FILE, "classpath:valid_models/DMNv1_x/Traffic Violation Simple.dmn") + .metadata(DMNTypeHandler.MODEL, "Traffic Violation") + .metadata(DMNTypeHandler.NAMESPACE, "https://github.com/kiegroup/drools/kie-dmn/_A4BCA8B8-CF08-433F-93B2-A2598F19ECFF"), args))) + .end().build(); + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn/pom.xml b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/pom.xml new file mode 100644 index 00000000000..7a8d68802b3 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + org.kie.kogito + kogito-serverless-workflow + 999-SNAPSHOT + + kogito-serverless-workflow-dmn + Kogito :: Serverless Workflow :: DMN :: Runtime + + org.kie.kogito.serverless.workflow.dmn.runtime + + + + + org.kie.kogito + kogito-serverless-workflow-runtime + + + org.kie.kogito + kogito-dmn + + + \ No newline at end of file diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/java/org/kie/kogito/serverless/workflow/dmn/SWFDecisionEngine.java b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/java/org/kie/kogito/serverless/workflow/dmn/SWFDecisionEngine.java new file mode 100644 index 00000000000..c84acfc652a --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/java/org/kie/kogito/serverless/workflow/dmn/SWFDecisionEngine.java @@ -0,0 +1,96 @@ +package org.kie.kogito.serverless.workflow.dmn; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jbpm.util.ContextFactory; +import org.jbpm.workflow.core.impl.NodeIoHelper; +import org.jbpm.workflow.core.node.RuleSetNode; +import org.jbpm.workflow.instance.node.RuleSetNodeInstance; +import org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine; +import org.kie.api.runtime.KieSession; +import org.kie.dmn.api.core.DMNContext; +import org.kie.dmn.api.core.DMNMessage; +import org.kie.dmn.api.core.DMNResult; +import org.kie.dmn.api.core.DMNRuntime; +import org.kie.kogito.decision.DecisionModel; +import org.kie.kogito.dmn.DmnDecisionModel; +import org.kie.kogito.dmn.rest.DMNJSONUtils; +import org.kie.kogito.internal.process.runtime.KogitoProcessContext; +import org.kie.kogito.jackson.utils.JsonObjectUtils; +import org.kie.kogito.process.expr.Expression; +import org.kie.kogito.process.expr.ExpressionHandlerFactory; +import org.kie.kogito.serverless.workflow.SWFConstants; + +import static org.kie.kogito.serverless.workflow.SWFConstants.CONTENT_DATA; + +public class SWFDecisionEngine implements DecisionRuleTypeEngine { + + public static final String EXPR_LANG = "lang"; + + @Override + public void evaluate(RuleSetNodeInstance rsni, String inputNamespace, String inputModel, String decision) { + String namespace = rsni.resolveExpression(inputNamespace); + String model = rsni.resolveExpression(inputModel); + DecisionModel modelInstance = + Optional.ofNullable(rsni.getRuleSetNode().getDecisionModel()) + .orElse(() -> new DmnDecisionModel( + ((KieSession) getKieRuntime(rsni)).getKieRuntime(DMNRuntime.class), + namespace, + model)) + .get(); + + //Input Binding + DMNContext context = DMNJSONUtils.ctx(modelInstance, getInputParameters(rsni)); + DMNResult dmnResult = modelInstance.evaluateAll(context); + if (dmnResult.hasErrors()) { + String errors = dmnResult.getMessages(DMNMessage.Severity.ERROR).stream() + .map(Object::toString) + .collect(Collectors.joining(", ")); + + throw new RuntimeException("DMN result errors:: " + errors); + } + //Output Binding + Map outputSet = Map.of(SWFConstants.RESULT, dmnResult.getContext().getAll()); + NodeIoHelper.processOutputs(rsni, outputSet::get, rsni::getVariable); + + rsni.triggerCompleted(); + } + + private Map getInputParameters(RuleSetNodeInstance rsni) { + RuleSetNode node = rsni.getRuleSetNode(); + Map inputParameters = node.getParameters(); + int size = inputParameters.size(); + if (size == 0) { + inputParameters = JsonObjectUtils.convertValue(getInputs(rsni).get(SWFConstants.MODEL_WORKFLOW_VAR), Map.class); + } else if (size == 1 && inputParameters.containsKey(CONTENT_DATA)) { + return eval(ContextFactory.fromNode(rsni), ExpressionHandlerFactory.get((String) node.getMetaData().get(EXPR_LANG), (String) inputParameters.get(CONTENT_DATA))); + } else { + inputParameters = getInputParameters(ContextFactory.fromNode(rsni), (String) node.getMetaData().get(EXPR_LANG), new HashMap<>(inputParameters)); + } + return inputParameters; + + } + + private Map getInputParameters(KogitoProcessContext context, String exprLang, Map inputParameters) { + for (Map.Entry entry : inputParameters.entrySet()) { + Object value = entry.getValue(); + if (value instanceof Map) { + entry.setValue(getInputParameters(context, exprLang, (Map) value)); + } else if (value instanceof CharSequence) { + Expression expr = ExpressionHandlerFactory.get(exprLang, value.toString()); + if (expr.isValid()) { + entry.setValue(eval(context, expr)); + } + } + } + return inputParameters; + } + + private Map eval(KogitoProcessContext context, Expression expr) { + return expr.eval(JsonObjectUtils.fromValue(context.getVariable(SWFConstants.DEFAULT_WORKFLOW_VAR)), Map.class, context); + } + +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/resources/META-INF/services/org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/resources/META-INF/services/org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine new file mode 100644 index 00000000000..077e0fb2c7d --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-dmn/src/main/resources/META-INF/services/org.jbpm.workflow.instance.rule.DecisionRuleTypeEngine @@ -0,0 +1 @@ +org.kie.kogito.serverless.workflow.dmn.SWFDecisionEngine \ No newline at end of file diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java b/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java index cc2182c414e..1abe1d2fca1 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/fluent/FunctionBuilder.java @@ -76,12 +76,15 @@ public static FunctionBuilder python(String funcName, String moduleName, String } private static FunctionBuilder service(String name, String langName, String moduleName, String methodName) { - return new FunctionBuilder(new FunctionDefinition(name).withType(Type.CUSTOM) - .withOperation(ServiceTypeHandler.SERVICE_TYPE + CUSTOM_TYPE_SEPARATOR + langName + CUSTOM_TYPE_SEPARATOR + moduleName + ServiceTypeHandler.INTFC_SEPARATOR + methodName)); + return custom(name, ServiceTypeHandler.SERVICE_TYPE + CUSTOM_TYPE_SEPARATOR + langName + CUSTOM_TYPE_SEPARATOR + moduleName + ServiceTypeHandler.INTFC_SEPARATOR + methodName); } public static FunctionBuilder log(String name, WorkflowLogLevel level) { - return new FunctionBuilder(new FunctionDefinition(name).withType(Type.CUSTOM).withOperation(SYSOUT_TYPE + CUSTOM_TYPE_SEPARATOR + level)); + return custom(name, SYSOUT_TYPE + CUSTOM_TYPE_SEPARATOR + level); + } + + public static FunctionBuilder custom(String name, String operation) { + return new FunctionBuilder(new FunctionDefinition(name).withType(Type.CUSTOM).withOperation(operation)); } private FunctionBuilder(FunctionDefinition functionDefinition) { diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java index 20a7b7d5ad0..3d822ab0be3 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/SWFConstants.java @@ -21,6 +21,7 @@ public class SWFConstants { public static final String DEFAULT_WORKFLOW_VAR = "workflowdata"; + public static final String RESULT = "Result"; public static final String MODEL_WORKFLOW_VAR = "Parameter"; public static final String CONTENT_DATA = "ContentData"; public static final String SCRIPT = "script"; diff --git a/kogito-serverless-workflow/pom.xml b/kogito-serverless-workflow/pom.xml index f752d16d206..cfca4a918e3 100644 --- a/kogito-serverless-workflow/pom.xml +++ b/kogito-serverless-workflow/pom.xml @@ -55,6 +55,8 @@ kogito-jq-expression kogito-serverless-workflow-executor kogito-serverless-workflow-executor-tests + kogito-serverless-workflow-dmn-parser + kogito-serverless-workflow-dmn diff --git a/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessor.java b/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessor.java index ab63a083fdb..346a0f5a40d 100644 --- a/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessor.java +++ b/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessor.java @@ -34,6 +34,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.drools.codegen.common.AppPaths; import org.drools.codegen.common.DroolsModelBuildContext; import org.drools.codegen.common.GeneratedFile; import org.drools.codegen.common.GeneratedFileType; @@ -107,10 +108,9 @@ public class KogitoAssetsProcessor { @BuildStep public KogitoBuildContextBuildItem generateKogitoBuildContext(List attributes) { // configure the application generator - PathCollection rootPaths = getRootPaths(root.getResolvedPaths()); + PathCollection rootPaths = getRootPaths(root.getResolvedPaths(), AppPaths.BT); KogitoBuildContext context = - kogitoBuildContext(outputTargetBuildItem.getOutputDirectory(), - rootPaths, + kogitoBuildContext(rootPaths, combinedIndexBuildItem.getIndex(), curateOutcomeBuildItem.getApplicationModel().getAppArtifact()); attributes.forEach(attribute -> context.addContextAttribute(attribute.getName(), attribute.getValue())); @@ -218,26 +218,26 @@ public EfestoGeneratedClassBuildItem reflectiveEfestoGeneratedClassBuildItem(Kog return new EfestoGeneratedClassBuildItem(kogitoGeneratedSourcesBuildItem.getGeneratedFiles()); } - static PathCollection getRootPaths(PathCollection resolvedPaths) { - AtomicReference toReturnRef = new AtomicReference<>(resolvedPaths); - if (resolvedPaths.stream().noneMatch(path -> path.endsWith(File.separator + "generated-resources"))) { - Optional optClassesPath = - resolvedPaths.stream().filter(path -> { - String fullPath = path.toString(); - String dir = fullPath.substring(fullPath.lastIndexOf(File.separator) + 1); - return dir.equals("classes"); - }).findFirst(); - optClassesPath.ifPresent(classesPath -> { - Path toAdd = Path.of(classesPath.toString().replace(File.separator + "classes", - File.separator + - "generated" + - "-resources")); - List prevPaths = toReturnRef.get().stream().collect(Collectors.toList()); - prevPaths.add(toAdd); - toReturnRef.set(PathList.from(prevPaths)); - }); + static PathCollection getRootPaths(PathCollection resolvedPaths, AppPaths.BuildTool bt) { + // Needed hack because during MAVEN build, resolvedPaths point to root of project, + // while during GRADLE build, resolved paths contains {root_project}/build/classes/java/main and + // {root_project}/build/resources/main + switch (bt) { + case GRADLE -> { + AtomicReference toReturnRef = new AtomicReference<>(resolvedPaths); + Optional optClassesPath = + resolvedPaths.stream().filter(path -> { + String fullPath = path.toString(); + String lookingFor = "build/classes/java/main".replace("/", File.separator); + return fullPath.endsWith(lookingFor); + }).findFirst(); + optClassesPath.ifPresent(classesPath -> toReturnRef.set(PathList.of(classesPath))); + return toReturnRef.get(); + } + default -> { + return resolvedPaths; + } } - return toReturnRef.get(); } private Collection collectGeneratedFiles(KogitoGeneratedSourcesBuildItem sources, List preSources, diff --git a/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoQuarkusResourceUtils.java b/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoQuarkusResourceUtils.java index 3b10caeaa95..6a83445f27e 100644 --- a/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoQuarkusResourceUtils.java +++ b/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoQuarkusResourceUtils.java @@ -34,6 +34,7 @@ import org.drools.codegen.common.DroolsModelBuildContext; import org.drools.codegen.common.GeneratedFile; import org.drools.codegen.common.GeneratedFileType; +import org.drools.codegen.common.GeneratedFileWriter; import org.drools.quarkus.util.deployment.QuarkusAppPaths; import org.drools.util.PortablePath; import org.eclipse.microprofile.config.ConfigProvider; @@ -44,7 +45,6 @@ import org.kie.kogito.codegen.api.SourceFileCodegenBindNotifier; import org.kie.kogito.codegen.api.context.KogitoBuildContext; import org.kie.kogito.codegen.api.context.impl.QuarkusKogitoBuildContext; -import org.kie.kogito.codegen.core.utils.GeneratedFileWriter; import org.kie.memorycompiler.resources.ResourceReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,16 +76,11 @@ private KogitoQuarkusResourceUtils() { // since quarkus-maven-plugin is later phase of maven-resources-plugin, // need to manually late-provide the resource in the expected location for quarkus:dev phase --so not: writeGeneratedFile( f, resourcePath ) - private static final GeneratedFileWriter.Builder generatedFileWriterBuilder = - new GeneratedFileWriter.Builder( - "target/classes", - System.getProperty("kogito.codegen.sources.directory", "target/generated-sources/kogito/"), - System.getProperty("kogito.codegen.resources.directory", "target/generated-resources/kogito/"), - "target/generated-sources/kogito/"); - - public static KogitoBuildContext kogitoBuildContext(Path outputTarget, Iterable paths, IndexView index, Dependency appArtifact) { + private static final GeneratedFileWriter.Builder generatedFileWriterBuilder = GeneratedFileWriter.builder("kogito", "kogito.codegen.resources.directory", "kogito.codegen.sources.directory"); + + public static KogitoBuildContext kogitoBuildContext(Iterable paths, IndexView index, Dependency appArtifact) { // scan and parse paths - AppPaths appPaths = QuarkusAppPaths.from(outputTarget, paths, AppPaths.BuildTool.findBuildTool()); + AppPaths appPaths = QuarkusAppPaths.from(paths); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); KogitoBuildContext context = QuarkusKogitoBuildContext.builder() .withApplicationPropertyProvider(new KogitoQuarkusApplicationPropertiesProvider()) @@ -111,9 +106,7 @@ public static KogitoBuildContext kogitoBuildContext(Path outputTarget, Iterable< private static Predicate> classSubTypeAvailabilityResolver(IndexView index) { return clazz -> index.getAllKnownImplementors(DotName.createSimple(clazz.getCanonicalName())) .stream() - .filter(c -> !Modifier.isInterface(c.flags()) && !Modifier.isAbstract(c.flags())) - .findFirst() - .isPresent(); + .anyMatch(c -> !Modifier.isInterface(c.flags()) && !Modifier.isAbstract(c.flags())); } /** diff --git a/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/test/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessorTest.java b/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/test/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessorTest.java index f641e28af93..fd7483bb677 100644 --- a/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/test/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessorTest.java +++ b/quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/test/java/org/kie/kogito/quarkus/common/deployment/KogitoAssetsProcessorTest.java @@ -22,7 +22,9 @@ import java.nio.file.Path; import java.util.Arrays; -import org.junit.jupiter.api.Test; +import org.drools.codegen.common.AppPaths; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.paths.PathCollection; @@ -32,34 +34,45 @@ class KogitoAssetsProcessorTest { - @Test - void getRootPathsWithoutClasses() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void getRootPathsWithoutClasses(boolean withGradle) { String projectDirPath = "projectDir"; - String outputTargetPath = "outputTarget"; Path projectDir = Path.of(projectDirPath); - Path outputTarget = Path.of(outputTargetPath); + AppPaths.BuildTool bt; + if (withGradle) { + bt = AppPaths.BuildTool.GRADLE; + } else { + bt = AppPaths.BuildTool.MAVEN; + } + Path outputTarget = Path.of(bt.OUTPUT_DIRECTORY); Iterable paths = Arrays.asList(projectDir, outputTarget); - PathCollection resolvedPaths = PathsCollection.from(paths); - PathCollection retrieved = KogitoAssetsProcessor.getRootPaths(resolvedPaths); + PathCollection retrieved = KogitoAssetsProcessor.getRootPaths(resolvedPaths, bt); assertEquals(resolvedPaths.size(), retrieved.size()); paths.forEach(expected -> assertTrue(retrieved.contains(expected))); } - @Test - void getRootPathsWithClasses() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void getRootPathsWithClasses(boolean withGradle) { String projectDirPath = "projectDir"; - String outputTargetPath = "outputTarget"; - String outputTargetPathClasses = String.format("%s/%s/classes", projectDirPath, outputTargetPath).replace("/", File.separator); + AppPaths.BuildTool bt; + if (withGradle) { + bt = AppPaths.BuildTool.GRADLE; + } else { + bt = AppPaths.BuildTool.MAVEN; + } + String outputTargetPathClasses = String.format("%s/%s", projectDirPath, bt.CLASSES_PATH.toString()).replace("./", "").replace("/", File.separator); Path projectDir = Path.of(projectDirPath); Path outputTarget = Path.of(outputTargetPathClasses); Iterable paths = Arrays.asList(projectDir, outputTarget); PathCollection resolvedPaths = PathsCollection.from(paths); - PathCollection retrieved = KogitoAssetsProcessor.getRootPaths(resolvedPaths); - assertEquals(resolvedPaths.size() + 1, retrieved.size()); - paths.forEach(expected -> assertTrue(retrieved.contains(expected))); - String expectedPath = String.format("%s/%s/generated-resources", projectDirPath, outputTargetPath).replace("/", File.separator); + PathCollection retrieved = KogitoAssetsProcessor.getRootPaths(resolvedPaths, bt); + int expectedSize = withGradle ? 1 : resolvedPaths.size(); + assertEquals(expectedSize, retrieved.size()); + String expectedPath = String.format("%s/%s", projectDirPath, bt.CLASSES_PATH).replace("/", File.separator); assertTrue(retrieved.contains(Path.of(expectedPath))); } }