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]

Reply via email to