This is an automated email from the ASF dual-hosted git repository.

martinweiler pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git


The following commit(s) were added to refs/heads/main by this push:
     new dc4977705df [kie-issues#7198] Support undeclared process variables 
(#7199)
dc4977705df is described below

commit dc4977705df96bb452d01896b2c4134daa550b0f
Author: Martin Weiler <[email protected]>
AuthorDate: Thu Oct 22 16:30:55 2026 -0600

    [kie-issues#7198] Support undeclared process variables (#7199)
    
    * [kie-issues#7198] Support undeclared process variables
    
    * Ensure the new additionalProperties fields are not part of the OpenAPI 
schema
    
    * Fix failing test
    
    * Remove config option
---
 .../src/main/java/org/kie/kogito/Model.java        |  20 ++++
 .../src/main/java/org/kie/kogito/Models.java       |  15 +++
 .../org/jbpm/compiler/canonical/ModelMetaData.java |  16 ++--
 .../canonical/ProcessToExecModelGenerator.java     |   3 +
 .../class-templates/ModelNoIDTemplate.java         |  33 +++++++
 .../resources/class-templates/ModelTemplate.java   |  18 ++++
 .../core/context/variable/VariableScope.java       |   2 +-
 .../process/impl/AbstractProcessInstance.java      |  21 ++++-
 .../config/KogitoWorkflowRuntimeConfig.java        |   2 +-
 .../src/main/resources/undeclared-variable.bpmn    | 104 +++++++++++++++++++++
 .../quarkus/UndeclaredVariableIT.java              |  77 +++++++++++++++
 .../src/main/resources/undeclared-variable.bpmn    | 104 +++++++++++++++++++++
 .../integrationtests/springboot/TaskTest.java      |   2 +-
 .../springboot/UndeclaredVariableTest.java         |  75 +++++++++++++++
 14 files changed, 481 insertions(+), 11 deletions(-)

diff --git a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java 
b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
index a4583aa74f5..b1b3c668e32 100644
--- a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
+++ b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
@@ -18,9 +18,12 @@
  */
 package org.kie.kogito;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
 /**
  * Represents data model type of objects that are usually descriptor of data 
holders.
  *
@@ -44,4 +47,21 @@ public interface Model extends MapInput, MapOutput {
     default Set<String> getModifiedFields() {
         return null;
     }
+
+    /**
+     * Returns the undeclared process variables carried by this model (legacy 
variable mode).
+     * Returns an empty map when the model does not carry any.
+     */
+    @JsonIgnore
+    default Map<String, Object> getAdditionalProperties() {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Sets the undeclared process variables carried by this model (legacy 
variable mode).
+     * No-op unless the model supports carrying them.
+     */
+    default void setAdditionalProperties(Map<String, Object> 
additionalProperties) {
+        // no-op by default
+    }
 }
diff --git a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java 
b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
index c91ca522fe2..860dda89ca4 100644
--- a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
+++ b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
@@ -50,6 +50,9 @@ public class Models {
                 }
             }
         }
+        if (m instanceof Model model) {
+            map.putAll(model.getAdditionalProperties());
+        }
         return map;
     }
 
@@ -60,10 +63,12 @@ public class Models {
 
     @SuppressWarnings("squid:S3011")
     public static <T> T fromMap(T m, Map<String, Object> map) {
+        Set<String> declaredJsonProperties = new java.util.HashSet<>();
         for (Field field : m.getClass().getDeclaredFields()) {
             JsonProperty jsonAnnotation = 
field.getAnnotation(JsonProperty.class);
             if (jsonAnnotation != null) {
                 String name = jsonAnnotation.value();
+                declaredJsonProperties.add(name);
                 if (map.containsKey(name)) {
                     field.setAccessible(true);
                     try {
@@ -74,6 +79,16 @@ public class Models {
                 }
             }
         }
+        if (m instanceof Model model) {
+            Map<String, Object> additionalProperties = new 
java.util.HashMap<>();
+            for (Map.Entry<String, Object> entry : map.entrySet()) {
+                String key = entry.getKey();
+                if (!declaredJsonProperties.contains(key)) {
+                    additionalProperties.put(key, entry.getValue());
+                }
+            }
+            model.setAdditionalProperties(additionalProperties);
+        }
         return m;
     }
 
diff --git 
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
 
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
index 1432ac379ad..85eb2250d8d 100644
--- 
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
+++ 
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
@@ -194,15 +194,16 @@ public class ModelMetaData {
         }
         modelClass.setName(modelClassSimpleName);
 
-        // The modified-fields tracking field/getter must stay out of the 
generated OpenAPI
+        // The modified-fields and additional-properties tracking members must 
stay out of the generated OpenAPI
         if (supportsOpenApiGeneration) {
             NormalAnnotationExpr hiddenSchema = new NormalAnnotationExpr(new 
Name(Schema.class.getCanonicalName()),
                     NodeList.nodeList(new MemberValuePair("hidden", new 
BooleanLiteralExpr(true))));
-            modelClass.findFirst(FieldDeclaration.class,
-                    fd -> fd.getVariables().stream().anyMatch(v -> 
v.getNameAsString().equals("__modifiedFields")))
-                    .ifPresent(fd -> fd.addAnnotation(hiddenSchema.clone()));
-            modelClass.findFirst(MethodDeclaration.class, md -> 
md.getNameAsString().equals("getModifiedFields"))
-                    .ifPresent(md -> md.addAnnotation(hiddenSchema.clone()));
+            modelClass.findAll(FieldDeclaration.class,
+                    fd -> fd.getVariables().stream().anyMatch(v -> 
v.getNameAsString().equals("__modifiedFields") || 
v.getNameAsString().equals("__additionalProperties")))
+                    .forEach(fd -> fd.addAnnotation(hiddenSchema.clone()));
+            modelClass.findAll(MethodDeclaration.class,
+                    md -> md.getNameAsString().equals("getModifiedFields") || 
md.getNameAsString().equals("getAdditionalProperties") || 
md.getNameAsString().equals("setAdditionalProperties"))
+                    .forEach(md -> md.addAnnotation(hiddenSchema.clone()));
             compilationUnit.addImport(Schema.class.getCanonicalName());
         }
 
@@ -262,6 +263,9 @@ public class ModelMetaData {
 
         Optional<MethodDeclaration> toMapMethod = 
modelClass.findFirst(MethodDeclaration.class, sl -> 
sl.getName().asString().equals("toMap"));
 
+        // Include any undeclared variables that were forwarded from the Input 
model.
+        toMapBody.addStatement(new MethodCallExpr(new NameExpr("params"), 
"putAll")
+                .addArgument(new MethodCallExpr(null, 
"getAdditionalProperties")));
         toMapBody.addStatement(new ReturnStmt(new NameExpr("params")));
         toMapMethod.ifPresent(methodDeclaration -> 
methodDeclaration.setBody(toMapBody));
 
diff --git 
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
 
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
index 1692b928f53..5441a76bb2c 100644
--- 
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
+++ 
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
@@ -224,6 +224,9 @@ public class ProcessToExecModelGenerator {
             }
             body.addStatement(new MethodCallExpr(returnName, 
"addModifiedFields",
                     NodeList.nodeList(new MethodCallExpr(null, 
"getModifiedFields"))));
+            // Forward undeclared variables captured by @JsonAnySetter on the 
Input model.
+            body.addStatement(new MethodCallExpr(returnName, 
"setAdditionalProperties",
+                    NodeList.nodeList(new MethodCallExpr(null, 
"getAdditionalProperties"))));
             body.addStatement(new ReturnStmt(returnName));
             method.setBody(body);
         }
diff --git 
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
 
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
index f66bd652d5a..5abc5051bdb 100644
--- 
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
+++ 
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
@@ -22,6 +22,7 @@ import org.kie.kogito.MapInput;
 import org.kie.kogito.MapInputId;
 import org.kie.kogito.MapOutput;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -30,6 +31,8 @@ import java.util.Set;
 import org.kie.kogito.MappableToModel;
 import org.kie.kogito.Model;
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
 public class XXXModel implements Model, MapInput, MapInputId, MapOutput,
@@ -39,6 +42,10 @@ public class XXXModel implements Model, MapInput, 
MapInputId, MapOutput,
     @JsonIgnore
     private transient final Set<String> __modifiedFields = new HashSet<>();
 
+    // Captures JSON properties that have no corresponding declared process 
variable field.
+    @JsonIgnore
+    private transient final Map<String, Object> __additionalProperties = new 
HashMap<>();
+
     @JsonIgnore
     @Override
     public Set<String> getModifiedFields() {
@@ -50,4 +57,30 @@ public class XXXModel implements Model, MapInput, 
MapInputId, MapOutput,
         __modifiedFields.add(field);
     }
 
+    @JsonAnySetter
+    void __setAdditionalProperty(String name, Object value) {
+        __additionalProperties.put(name, value);
+    }
+
+    @JsonAnyGetter
+    Map<String, Object> __getAdditionalProperties() {
+        return Collections.unmodifiableMap(__additionalProperties);
+    }
+
+    @JsonIgnore
+    @Override
+    public Map<String, Object> getAdditionalProperties() {
+        return __additionalProperties;
+    }
+
+    // Overridden only so the setter can be hidden from JSON and OpenAPI, like 
the getter.
+    @JsonIgnore
+    @Override
+    public void setAdditionalProperties(Map<String, Object> props) {
+        __additionalProperties.clear();
+        if (props != null) {
+            __additionalProperties.putAll(props);
+        }
+    }
+
 }
diff --git 
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
 
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
index b74cb6e7b38..aa9ffa2eb9c 100644
--- 
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
+++ 
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
@@ -22,6 +22,7 @@ import org.kie.kogito.MapInput;
 import org.kie.kogito.MapInputId;
 import org.kie.kogito.MapOutput;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -39,6 +40,10 @@ public class XXXModel implements org.kie.kogito.Model, 
MapInput, MapInputId, Map
     @JsonIgnore
     private transient Set<String> __modifiedFields;
 
+    // Carries undeclared process variables forwarded from the Input model.
+    @JsonIgnore
+    private transient Map<String, Object> __additionalProperties;
+
     public void setId(String id) {
         this.id = id;
     }
@@ -71,4 +76,17 @@ public class XXXModel implements org.kie.kogito.Model, 
MapInput, MapInputId, Map
         __modifiedFields = null;
     }
 
+    // Codegen-internal: forwards undeclared variables from the Input model 
into this model.
+    @JsonIgnore
+    @Override
+    public void setAdditionalProperties(Map<String, Object> props) {
+        __additionalProperties = props;
+    }
+
+    @JsonIgnore
+    @Override
+    public Map<String, Object> getAdditionalProperties() {
+        return __additionalProperties != null ? __additionalProperties : 
Collections.emptyMap();
+    }
+
 }
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/context/variable/VariableScope.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/context/variable/VariableScope.java
index 613926aaf7d..2df98525558 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/context/variable/VariableScope.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/context/variable/VariableScope.java
@@ -85,7 +85,7 @@ public class VariableScope extends AbstractContext {
         }
         Variable var = findVariable(name);
         if (var == null) {
-            throw new IllegalArgumentException("Variable '" + name + "' is not 
defined in process " + processName);
+            return;
         }
         if (var.getType() != null && value != null) {
             boolean isValidType = var.getType().verifyDataType(value);
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
index e7925c88cab..4b48f9d45f4 100644
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
@@ -835,9 +835,9 @@ public abstract class AbstractProcessInstance<T extends 
Model> implements Proces
         try {
             for (Field f : variables.getClass().getDeclaredFields()) {
                 f.setAccessible(true);
-                Object v = f.get(variables);
-                vmap.put(f.getName(), v);
+                vmap.put(f.getName(), f.get(variables));
             }
+            vmap.putAll(variables.getAdditionalProperties());
         } catch (IllegalAccessException e) {
             throw new Error(e);
         }
@@ -855,12 +855,29 @@ public abstract class AbstractProcessInstance<T extends 
Model> implements Proces
                 f.setAccessible(true);
                 f.set(variables, vmap.get(f.getName()));
             }
+            Map<String, Object> additionalProperties = new HashMap<>();
+            for (Map.Entry<String, Object> entry : vmap.entrySet()) {
+                String key = entry.getKey();
+                if (!"$v".equals(key) && 
!isDeclaredField(variables.getClass(), key)) {
+                    additionalProperties.put(key, entry.getValue());
+                }
+            }
+            variables.setAdditionalProperties(additionalProperties);
         } catch (IllegalAccessException e) {
             throw new Error(e);
         }
         vmap.put("$v", variables);
     }
 
+    private boolean isDeclaredField(Class<?> clazz, String fieldName) {
+        try {
+            clazz.getDeclaredField(fieldName);
+            return true;
+        } catch (NoSuchFieldException e) {
+            return false;
+        }
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
diff --git 
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/config/KogitoWorkflowRuntimeConfig.java
 
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/config/KogitoWorkflowRuntimeConfig.java
index acdd2281e4a..7063df5640d 100644
--- 
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/config/KogitoWorkflowRuntimeConfig.java
+++ 
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/config/KogitoWorkflowRuntimeConfig.java
@@ -31,6 +31,6 @@ public interface KogitoWorkflowRuntimeConfig {
      * Persistence runtime configuration
      */
     @WithName("persistence")
-    public KogitoPersistenceRuntimeConfig persistenceConfig();
+    KogitoPersistenceRuntimeConfig persistenceConfig();
 
 }
diff --git 
a/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/main/resources/undeclared-variable.bpmn
 
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/main/resources/undeclared-variable.bpmn
new file mode 100644
index 00000000000..9b8ae85b840
--- /dev/null
+++ 
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/main/resources/undeclared-variable.bpmn
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL";
+                   xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+                   xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+                   xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+                   xmlns:drools="http://www.jboss.org/drools";
+                   id="_undeclared_variable_def"
+                   targetNamespace="http://www.omg.org/bpmn20";
+                   
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd 
http://www.jboss.org/drools drools.xsd">
+
+  <bpmn2:signal id="_sig_continue" name="continue"/>
+
+  <!--
+    Process: undeclaredVariable
+    Variables declared: none
+    Logic:
+      Start → XOR gateway
+        → if kcontext.getVariable("payload") == null  → End (null-path)
+        → otherwise                                    → Intermediate catch 
(signal "continue") → End (wait-path)
+    The undeclared variable "payload" is passed as an input parameter when the 
process starts.
+  -->
+  <bpmn2:process id="undeclaredVariable"
+                 drools:packageName="org.kie.kogito.integrationtests"
+                 drools:version="1.0"
+                 drools:adHoc="false"
+                 name="undeclaredVariable"
+                 isExecutable="true"
+                 processType="Public">
+
+    <!-- sequence flows -->
+    <bpmn2:sequenceFlow id="_sf_start_gw"     sourceRef="_start"  
targetRef="_gw"/>
+    <bpmn2:sequenceFlow id="_sf_gw_wait"      sourceRef="_gw"     
targetRef="_wait">
+      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" 
language="http://www.java.com/java";>return kcontext.getVariable("payload") != 
null;</bpmn2:conditionExpression>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_sf_gw_end_null"  sourceRef="_gw"     
targetRef="_end_null">
+      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" 
language="http://www.java.com/java";>return kcontext.getVariable("payload") == 
null;</bpmn2:conditionExpression>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_sf_wait_end"     sourceRef="_wait"   
targetRef="_end_wait"/>
+
+    <!-- start event -->
+    <bpmn2:startEvent id="_start" name="Start">
+      <bpmn2:outgoing>_sf_start_gw</bpmn2:outgoing>
+    </bpmn2:startEvent>
+
+    <!-- exclusive gateway -->
+    <bpmn2:exclusiveGateway id="_gw" name="Payload provided?" 
gatewayDirection="Diverging">
+      <bpmn2:incoming>_sf_start_gw</bpmn2:incoming>
+      <bpmn2:outgoing>_sf_gw_wait</bpmn2:outgoing>
+      <bpmn2:outgoing>_sf_gw_end_null</bpmn2:outgoing>
+    </bpmn2:exclusiveGateway>
+
+    <!-- intermediate catch event – waits for signal "continue" -->
+    <bpmn2:intermediateCatchEvent id="_wait" name="Wait for continue">
+      <bpmn2:incoming>_sf_gw_wait</bpmn2:incoming>
+      <bpmn2:outgoing>_sf_wait_end</bpmn2:outgoing>
+      <bpmn2:signalEventDefinition signalRef="_sig_continue"/>
+    </bpmn2:intermediateCatchEvent>
+
+    <!-- end event: reached when payload is null -->
+    <bpmn2:endEvent id="_end_null" name="End (null)">
+      <bpmn2:incoming>_sf_gw_end_null</bpmn2:incoming>
+    </bpmn2:endEvent>
+
+    <!-- end event: reached after signal is received -->
+    <bpmn2:endEvent id="_end_wait" name="End (signal)">
+      <bpmn2:incoming>_sf_wait_end</bpmn2:incoming>
+    </bpmn2:endEvent>
+
+  </bpmn2:process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="undeclaredVariable">
+      <bpmndi:BPMNShape id="_start_di"     bpmnElement="_start">    <dc:Bounds 
x="100" y="100" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_gw_di"        bpmnElement="_gw">       <dc:Bounds 
x="200" y="93"  width="50" height="50"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_wait_di"      bpmnElement="_wait">     <dc:Bounds 
x="320" y="100" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_end_null_di"  bpmnElement="_end_null"> <dc:Bounds 
x="320" y="200" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_end_wait_di"  bpmnElement="_end_wait"> <dc:Bounds 
x="440" y="100" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge id="_sf_start_gw_di"    bpmnElement="_sf_start_gw"/>
+      <bpmndi:BPMNEdge id="_sf_gw_wait_di"     bpmnElement="_sf_gw_wait"/>
+      <bpmndi:BPMNEdge id="_sf_gw_end_null_di" bpmnElement="_sf_gw_end_null"/>
+      <bpmndi:BPMNEdge id="_sf_wait_end_di"    bpmnElement="_sf_wait_end"/>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</bpmn2:definitions>
diff --git 
a/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/UndeclaredVariableIT.java
 
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/UndeclaredVariableIT.java
new file mode 100644
index 00000000000..115189a8bd2
--- /dev/null
+++ 
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/UndeclaredVariableIT.java
@@ -0,0 +1,77 @@
+/*
+ * 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.integrationtests.quarkus;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+/**
+ * Verifies that a process can accept an undeclared variable as input (always 
enabled by default).
+ * The BPMN "undeclaredVariable" has no declared process variables.
+ * When started with a "payload" key in the body the process enters a 
signal-wait state and stays ACTIVE.
+ */
+@QuarkusIntegrationTest
+class UndeclaredVariableIT {
+
+    static {
+        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+    }
+
+    @Test
+    void testUndeclaredVariableKeepsProcessActive() {
+        // Start the process with an undeclared variable – must succeed 
(undeclared variables are supported by default)
+        String pid = given()
+                .contentType(ContentType.JSON)
+                .body(Collections.singletonMap("payload", "someValue"))
+                .when()
+                .post("/undeclaredVariable")
+                .then()
+                .statusCode(201)
+                .body("id", not(emptyOrNullString()))
+                .extract()
+                .path("id");
+
+        // The process should still be ACTIVE (waiting at the signal catch 
event)
+        given()
+                .contentType(ContentType.JSON)
+                .when()
+                .get("/undeclaredVariable/{pid}", pid)
+                .then()
+                .statusCode(200)
+                .body("id", equalTo(pid));
+
+        // Clean up – abort the process instance
+        given()
+                .contentType(ContentType.JSON)
+                .when()
+                .delete("/undeclaredVariable/{pid}", pid)
+                .then()
+                .statusCode(200);
+    }
+}
diff --git 
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/undeclared-variable.bpmn
 
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/undeclared-variable.bpmn
new file mode 100644
index 00000000000..9b8ae85b840
--- /dev/null
+++ 
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/undeclared-variable.bpmn
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL";
+                   xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+                   xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+                   xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+                   xmlns:drools="http://www.jboss.org/drools";
+                   id="_undeclared_variable_def"
+                   targetNamespace="http://www.omg.org/bpmn20";
+                   
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd 
http://www.jboss.org/drools drools.xsd">
+
+  <bpmn2:signal id="_sig_continue" name="continue"/>
+
+  <!--
+    Process: undeclaredVariable
+    Variables declared: none
+    Logic:
+      Start → XOR gateway
+        → if kcontext.getVariable("payload") == null  → End (null-path)
+        → otherwise                                    → Intermediate catch 
(signal "continue") → End (wait-path)
+    The undeclared variable "payload" is passed as an input parameter when the 
process starts.
+  -->
+  <bpmn2:process id="undeclaredVariable"
+                 drools:packageName="org.kie.kogito.integrationtests"
+                 drools:version="1.0"
+                 drools:adHoc="false"
+                 name="undeclaredVariable"
+                 isExecutable="true"
+                 processType="Public">
+
+    <!-- sequence flows -->
+    <bpmn2:sequenceFlow id="_sf_start_gw"     sourceRef="_start"  
targetRef="_gw"/>
+    <bpmn2:sequenceFlow id="_sf_gw_wait"      sourceRef="_gw"     
targetRef="_wait">
+      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" 
language="http://www.java.com/java";>return kcontext.getVariable("payload") != 
null;</bpmn2:conditionExpression>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_sf_gw_end_null"  sourceRef="_gw"     
targetRef="_end_null">
+      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" 
language="http://www.java.com/java";>return kcontext.getVariable("payload") == 
null;</bpmn2:conditionExpression>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_sf_wait_end"     sourceRef="_wait"   
targetRef="_end_wait"/>
+
+    <!-- start event -->
+    <bpmn2:startEvent id="_start" name="Start">
+      <bpmn2:outgoing>_sf_start_gw</bpmn2:outgoing>
+    </bpmn2:startEvent>
+
+    <!-- exclusive gateway -->
+    <bpmn2:exclusiveGateway id="_gw" name="Payload provided?" 
gatewayDirection="Diverging">
+      <bpmn2:incoming>_sf_start_gw</bpmn2:incoming>
+      <bpmn2:outgoing>_sf_gw_wait</bpmn2:outgoing>
+      <bpmn2:outgoing>_sf_gw_end_null</bpmn2:outgoing>
+    </bpmn2:exclusiveGateway>
+
+    <!-- intermediate catch event – waits for signal "continue" -->
+    <bpmn2:intermediateCatchEvent id="_wait" name="Wait for continue">
+      <bpmn2:incoming>_sf_gw_wait</bpmn2:incoming>
+      <bpmn2:outgoing>_sf_wait_end</bpmn2:outgoing>
+      <bpmn2:signalEventDefinition signalRef="_sig_continue"/>
+    </bpmn2:intermediateCatchEvent>
+
+    <!-- end event: reached when payload is null -->
+    <bpmn2:endEvent id="_end_null" name="End (null)">
+      <bpmn2:incoming>_sf_gw_end_null</bpmn2:incoming>
+    </bpmn2:endEvent>
+
+    <!-- end event: reached after signal is received -->
+    <bpmn2:endEvent id="_end_wait" name="End (signal)">
+      <bpmn2:incoming>_sf_wait_end</bpmn2:incoming>
+    </bpmn2:endEvent>
+
+  </bpmn2:process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="undeclaredVariable">
+      <bpmndi:BPMNShape id="_start_di"     bpmnElement="_start">    <dc:Bounds 
x="100" y="100" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_gw_di"        bpmnElement="_gw">       <dc:Bounds 
x="200" y="93"  width="50" height="50"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_wait_di"      bpmnElement="_wait">     <dc:Bounds 
x="320" y="100" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_end_null_di"  bpmnElement="_end_null"> <dc:Bounds 
x="320" y="200" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="_end_wait_di"  bpmnElement="_end_wait"> <dc:Bounds 
x="440" y="100" width="36" height="36"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge id="_sf_start_gw_di"    bpmnElement="_sf_start_gw"/>
+      <bpmndi:BPMNEdge id="_sf_gw_wait_di"     bpmnElement="_sf_gw_wait"/>
+      <bpmndi:BPMNEdge id="_sf_gw_end_null_di" bpmnElement="_sf_gw_end_null"/>
+      <bpmndi:BPMNEdge id="_sf_wait_end_di"    bpmnElement="_sf_wait_end"/>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</bpmn2:definitions>
diff --git 
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
 
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
index ef871635433..3d6f87a3970 100644
--- 
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
+++ 
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java
@@ -108,7 +108,7 @@ public class TaskTest extends BaseRestTest {
 
     @Test
     void testJsonSchemaFiles() {
-        long expectedJsonSchemas = 33;
+        long expectedJsonSchemas = 34;
         Path jsonDir = Paths.get("target", 
"classes").resolve(JsonSchemaUtil.getJsonDir());
         try (Stream<Path> paths = Files.walk(jsonDir)) {
             long generatedJsonSchemas = paths
diff --git 
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/UndeclaredVariableTest.java
 
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/UndeclaredVariableTest.java
new file mode 100644
index 00000000000..5a3150c117b
--- /dev/null
+++ 
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/UndeclaredVariableTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.integrationtests.springboot;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+/**
+ * Verifies that a process can accept an undeclared variable as input (always 
enabled by default).
+ * The BPMN "undeclaredVariable" has no declared process variables.
+ * When started with a "payload" key in the body the process enters a 
signal-wait state and stays ACTIVE.
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 
classes = KogitoSpringbootApplication.class)
+class UndeclaredVariableTest extends BaseRestTest {
+
+    @Test
+    void testUndeclaredVariableKeepsProcessActive() {
+        // Start the process with an undeclared variable – must succeed 
(undeclared variables are supported by default)
+        String pid = given()
+                .contentType(ContentType.JSON)
+                .body(Collections.singletonMap("payload", "someValue"))
+                .when()
+                .post("/undeclaredVariable")
+                .then()
+                .statusCode(201)
+                .body("id", not(emptyOrNullString()))
+                .extract()
+                .path("id");
+
+        // The process should still be ACTIVE (waiting at the signal catch 
event)
+        given()
+                .contentType(ContentType.JSON)
+                .when()
+                .get("/undeclaredVariable/{pid}", pid)
+                .then()
+                .statusCode(200)
+                .body("id", equalTo(pid));
+
+        // Clean up – abort the process instance
+        given()
+                .contentType(ContentType.JSON)
+                .when()
+                .delete("/undeclaredVariable/{pid}", pid)
+                .then()
+                .statusCode(200);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to