This is an automated email from the ASF dual-hosted git repository.
josedee pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-runtimes.git
The following commit(s) were added to refs/heads/main by this push:
new 8538e15a2f [incubator-kie-issues#2324] Record Service Task I/O Data
(#4303)
8538e15a2f is described below
commit 8538e15a2f9e9d05f2886dea119d1848e0e4e429
Author: Deepak Joseph <[email protected]>
AuthorDate: Thu Jun 18 13:23:36 2026 +0530
[incubator-kie-issues#2324] Record Service Task I/O Data (#4303)
* Record Service Task I/O Data
* Global property for recording i/o
* Fixes
* Subprocess checks
* Optimized imports
* changed property to static constant
* Changed global property
* Typo
---
api/kogito-api/pom.xml | 10 +
.../process/workitem/WorkItemRecordParameters.java | 43 +++-
.../workitem/WorkItemRecordParametersTest.java | 273 +++++++++++++++++++++
.../kie/kogito/codegen/process/ProcessCodegen.java | 26 ++
4 files changed, 351 insertions(+), 1 deletion(-)
diff --git a/api/kogito-api/pom.xml b/api/kogito-api/pom.xml
index 1d5ab20d11..e4025dbe28 100755
--- a/api/kogito-api/pom.xml
+++ b/api/kogito-api/pom.xml
@@ -92,6 +92,16 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git
a/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParameters.java
b/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParameters.java
index 0fcdf6221c..26cffa2310 100644
---
a/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParameters.java
+++
b/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParameters.java
@@ -22,6 +22,9 @@ import java.util.HashMap;
import java.util.Map;
import org.kie.api.definition.process.Node;
+import org.kie.api.definition.process.NodeContainer;
+import org.kie.api.definition.process.Process;
+import org.kie.kogito.internal.process.runtime.KogitoNode;
import org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
public class WorkItemRecordParameters {
@@ -76,7 +79,45 @@ public class WorkItemRecordParameters {
}
private static boolean shouldRecordParameters(Node node) {
- return node != null &&
shouldRecordParameters(node.getMetaData().getOrDefault(RECORD_ARGS, false));
+ if (node == null) {
+ return false;
+ }
+
+ // Check node-level metadata (highest priority)
+ Object nodeValue = node.getMetaData().get(RECORD_ARGS);
+ if (nodeValue != null) {
+ return shouldRecordParameters(nodeValue);
+ }
+
+ // Check process-level metadata by traversing up the container
hierarchy
+ if (node instanceof KogitoNode kogitoNode) {
+ Process process = findProcess(kogitoNode);
+ if (process != null) {
+ Object processValue = process.getMetaData().get(RECORD_ARGS);
+ if (processValue != null) {
+ return shouldRecordParameters(processValue);
+ }
+ }
+ }
+
+ // Default to false
+ return false;
+ }
+
+ private static Process findProcess(KogitoNode node) {
+ NodeContainer container = node.getParentContainer();
+ while (container != null) {
+ if (container instanceof Process process) {
+ return process;
+ }
+ // Traverse up if the container is also a KogitoNode (e.g.,
subprocess)
+ if (container instanceof KogitoNode kogitoContainerNode) {
+ container = kogitoContainerNode.getParentContainer();
+ } else {
+ break;
+ }
+ }
+ return null;
}
private static boolean shouldRecordParameters(Object value) {
diff --git
a/api/kogito-api/src/test/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParametersTest.java
b/api/kogito-api/src/test/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParametersTest.java
new file mode 100644
index 0000000000..c637f3919f
--- /dev/null
+++
b/api/kogito-api/src/test/java/org/kie/kogito/internal/process/workitem/WorkItemRecordParametersTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.internal.process.workitem;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.api.definition.process.NodeContainer;
+import org.kie.kogito.internal.process.runtime.KogitoNode;
+import org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static
org.kie.kogito.internal.process.workitem.WorkItemRecordParameters.RECORD_ARGS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class WorkItemRecordParametersTest {
+
+ @Mock
+ private KogitoWorkItem workItem;
+
+ @Mock
+ private KogitoNodeInstance nodeInstance;
+
+ @Mock
+ private KogitoNode node;
+
+ @Test
+ void testRecordInputParameters_WithWorkItem_NodeMetadataTrue() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, true);
+
+ Map<String, Object> nodeInstanceMetadata = new HashMap<>();
+ Map<String, Object> inputArgs = Map.of("param1", "value1");
+
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(nodeInstance.getMetaData()).thenReturn(nodeInstanceMetadata);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+
+ assertThat(nodeInstanceMetadata).isEmpty();
+
+ WorkItemRecordParameters.recordInputParameters(workItem, inputArgs);
+
+ verify(node).getMetaData();
+ verify(nodeInstance).getMetaData();
+ assertThat(nodeInstanceMetadata).hasSize(1);
+ assertThat(nodeInstanceMetadata).containsEntry("inputArgs", inputArgs);
+ }
+
+ @Test
+ void testRecordInputParameters_WithWorkItem_NodeMetadataFalse() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, false);
+ Map<String, Object> inputArgs = Map.of("param1", "value1");
+
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+
+ WorkItemRecordParameters.recordInputParameters(workItem, inputArgs);
+
+ verify(nodeInstance, never()).getMetaData();
+ }
+
+ @Test
+ void testRecordInputParameters_WithArgNames_RecordsSpecifiedArgs() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, true);
+
+ Map<String, Object> nodeInstanceMetadata = new HashMap<>();
+
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(nodeInstance.getMetaData()).thenReturn(nodeInstanceMetadata);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+ when(workItem.getParameter("arg1")).thenReturn("value1");
+ when(workItem.getParameter("arg2")).thenReturn(42);
+
+ assertThat(nodeInstanceMetadata).isEmpty();
+
+ WorkItemRecordParameters.recordInputParameters(workItem, "arg1",
"arg2");
+
+ verify(node).getMetaData();
+ verify(workItem).getParameter("arg1");
+ verify(workItem).getParameter("arg2");
+ verify(nodeInstance).getMetaData();
+ assertThat(nodeInstanceMetadata).hasSize(1);
+ @SuppressWarnings("unchecked")
+ Map<String, Object> recorded = (Map<String, Object>)
nodeInstanceMetadata.get("inputArgs");
+ assertThat(recorded).containsEntry("arg1", "value1");
+ assertThat(recorded).containsEntry("arg2", 42);
+ }
+
+ @Test
+ void testRecordInputParameters_WithEmptyArgNames_DoesNotRecord() {
+ WorkItemRecordParameters.recordInputParameters(workItem);
+
+ verify(workItem, never()).getNodeInstance();
+ }
+
+ @Test
+ void testRecordOutputParameters_WithWorkItem_ExtractsResult() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, true);
+
+ Map<String, Object> nodeInstanceMetadata = new HashMap<>();
+ Map<String, Object> outputArgs = Map.of("Result", "success", "other",
"value");
+
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(nodeInstance.getMetaData()).thenReturn(nodeInstanceMetadata);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+
+ assertThat(nodeInstanceMetadata).isEmpty();
+
+ WorkItemRecordParameters.recordOutputParameters(workItem, outputArgs);
+
+ verify(node).getMetaData();
+ verify(nodeInstance).getMetaData();
+ assertThat(nodeInstanceMetadata).hasSize(1);
+ assertThat(nodeInstanceMetadata).containsEntry("outputArgs",
"success");
+ }
+
+ @Test
+ void testRecordOutputParameters_WithWorkItem_NoResult_UsesFullMap() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, true);
+
+ Map<String, Object> nodeInstanceMetadata = new HashMap<>();
+ Map<String, Object> outputArgs = Map.of("status", "completed");
+
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(nodeInstance.getMetaData()).thenReturn(nodeInstanceMetadata);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+
+ assertThat(nodeInstanceMetadata).isEmpty();
+
+ WorkItemRecordParameters.recordOutputParameters(workItem, outputArgs);
+
+ verify(node).getMetaData();
+ verify(nodeInstance).getMetaData();
+ assertThat(nodeInstanceMetadata).hasSize(1);
+ assertThat(nodeInstanceMetadata).containsEntry("outputArgs",
outputArgs);
+ }
+
+ @Test
+ void testRecordInputParameters_ProcessMetadataFallback() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ Map<String, Object> nodeInstanceMetadata = new HashMap<>();
+ Map<String, Object> processMetadata = new HashMap<>();
+ processMetadata.put(RECORD_ARGS, true);
+ Map<String, Object> inputArgs = Map.of("param1", "value1");
+
+ NodeContainer parentContainer = mock(NodeContainer.class,
+
org.mockito.Mockito.withSettings().extraInterfaces(org.kie.api.definition.process.Process.class));
+ when(((org.kie.api.definition.process.Process)
parentContainer).getMetaData()).thenReturn(processMetadata);
+
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(nodeInstance.getMetaData()).thenReturn(nodeInstanceMetadata);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+ when(((KogitoNode)
node).getParentContainer()).thenReturn(parentContainer);
+
+ assertThat(nodeInstanceMetadata).isEmpty();
+
+ WorkItemRecordParameters.recordInputParameters(workItem, inputArgs);
+
+ verify(node).getMetaData();
+ verify((org.kie.api.definition.process.Process)
parentContainer).getMetaData();
+ assertThat(nodeInstanceMetadata).hasSize(1);
+ assertThat(nodeInstanceMetadata).containsEntry("inputArgs", inputArgs);
+ }
+
+ @Test
+ void testBooleanConversion_InvalidString() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, "invalid");
+ Object inputArgs = Map.of("param1", "value1");
+
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+
+ WorkItemRecordParameters.recordInputParameters(nodeInstance,
inputArgs);
+
+ verify(nodeInstance, never()).getMetaData();
+ }
+
+ @Test
+ void testBooleanConversion_NonBooleanNonString() {
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ nodeMetadata.put(RECORD_ARGS, 123);
+ Object inputArgs = Map.of("param1", "value1");
+
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+
+ WorkItemRecordParameters.recordInputParameters(nodeInstance,
inputArgs);
+
+ verify(nodeInstance, never()).getMetaData();
+ }
+
+ @Test
+ void testRecordInputParameters_NullNode() {
+ Object inputArgs = Map.of("param1", "value1");
+
+ when(nodeInstance.getNode()).thenReturn(null);
+
+ WorkItemRecordParameters.recordInputParameters(nodeInstance,
inputArgs);
+
+ verify(nodeInstance, never()).getMetaData();
+ }
+
+ @Test
+ void testRecordInputParameters_ProcessMetadataFallback_WithSubprocess() {
+ // Test that process-level metadata is found even when node is inside
a subprocess
+ Map<String, Object> nodeMetadata = new HashMap<>();
+ Map<String, Object> nodeInstanceMetadata = new HashMap<>();
+ Map<String, Object> processMetadata = new HashMap<>();
+ processMetadata.put(RECORD_ARGS, true);
+ Map<String, Object> inputArgs = Map.of("param1", "value1");
+
+ // Create a subprocess (NodeContainer that is also a KogitoNode)
+ NodeContainer subprocess = mock(NodeContainer.class,
+
org.mockito.Mockito.withSettings().extraInterfaces(KogitoNode.class));
+
+ // Create the process
+ NodeContainer process = mock(NodeContainer.class,
+
org.mockito.Mockito.withSettings().extraInterfaces(org.kie.api.definition.process.Process.class));
+ when(((org.kie.api.definition.process.Process)
process).getMetaData()).thenReturn(processMetadata);
+
+ // Setup hierarchy: node -> subprocess -> process
+ when(workItem.getNodeInstance()).thenReturn(nodeInstance);
+ when(nodeInstance.getNode()).thenReturn(node);
+ when(nodeInstance.getMetaData()).thenReturn(nodeInstanceMetadata);
+ when(node.getMetaData()).thenReturn(nodeMetadata);
+ when(((KogitoNode) node).getParentContainer()).thenReturn(subprocess);
+ when(((KogitoNode)
subprocess).getParentContainer()).thenReturn(process);
+
+ assertThat(nodeInstanceMetadata).isEmpty();
+
+ WorkItemRecordParameters.recordInputParameters(workItem, inputArgs);
+
+ verify(node).getMetaData();
+ verify((org.kie.api.definition.process.Process) process).getMetaData();
+ assertThat(nodeInstanceMetadata).hasSize(1);
+ assertThat(nodeInstanceMetadata).containsEntry("inputArgs", inputArgs);
+ }
+}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
index b83966136f..f80633afbf 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java
@@ -118,6 +118,7 @@ public class ProcessCodegen extends AbstractGenerator {
public static final String SOURCE_FILE_PROVIDER_PRODUCER =
"SourceFilesProviderProducer";
private static final String IS_BUSINESS_CALENDAR_PRESENT =
"isBusinessCalendarPresent";
+ private static final String RECORD_NODES_IO_GLOBAL_PROPERTY =
"kogito.processes.nodes.record-io";
static {
ProcessValidatorRegistry.getInstance().registerAdditonalValidator(JavaRuleFlowProcessValidator.getInstance());
@@ -187,6 +188,28 @@ public class ProcessCodegen extends AbstractGenerator {
.ifPresent(notifier -> processes.forEach(p ->
notifier.notify(new SourceFileCodegenBindEvent(p.getId(),
resource.getSourcePath()))));
}
+ /**
+ * Injects recordArgs metadata into a process if the global property is
enabled.
+ * This allows the global property defined by
RECORD_NODES_IO_GLOBAL_PROPERTY to control
+ * input/output argument recording for all nodes in the process.
+ */
+ private void injectRecordArgsMetadataIfNeeded(WorkflowProcess process) {
+ final String recordArgs = "recordArgs";
+
+ boolean globalRecordArgs =
context().getApplicationProperty(RECORD_NODES_IO_GLOBAL_PROPERTY, Boolean.class)
+ .orElse(false);
+
+ if (!globalRecordArgs) {
+ return;
+ }
+
+ // Only inject if process doesn't already have recordArgs metadata
+ if (process.getMetaData().get(recordArgs) == null) {
+ ((WorkflowProcessImpl) process).setMetaData(recordArgs, true);
+ LOGGER.debug("Injected recordArgs=true metadata into process: {}",
process.getId());
+ }
+ }
+
private static void handleValidation(KogitoBuildContext context,
Map<String, Throwable> processesErrors) {
if (!processesErrors.isEmpty()) {
ValidationLogDecorator decorator = new
ValidationLogDecorator(processesErrors);
@@ -314,6 +337,9 @@ public class ProcessCodegen extends AbstractGenerator {
((WorkflowProcessImpl)
workFlowProcess).setMetaData(WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS.getName(),
"true");
}
+ // Inject recordArgs metadata if global property is enabled
+ injectRecordArgsMetadataIfNeeded(workFlowProcess);
+
if (!skipModelGeneration(workFlowProcess)) {
ModelClassGenerator mcg = new ModelClassGenerator(context(),
workFlowProcess);
processIdToModelGenerator.put(workFlowProcess.getId(), mcg);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]