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]