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-drools.git
The following commit(s) were added to refs/heads/main by this push:
new 7616139f126 [incubator-kie-issue-2381] Support dynamic process calling
(#6824)
7616139f126 is described below
commit 7616139f1263a18b81537163a4da8a8ae8837dbb
Author: Christine-Jose <[email protected]>
AuthorDate: Fri Jul 24 20:20:48 2026 +0530
[incubator-kie-issue-2381] Support dynamic process calling (#6824)
* Changes for dynamic subprocess calling
* Review comments
* Fixing failed tests
* reverting the NodeCreator.java
* Adding the fix for java.lang.NoSuchMethodException;
---
.../kogito/codegen/tests/CallActivityTaskIT.java | 34 ++
.../process/process-generation-test.skip.txt | 2 +
.../subprocess/CallActivityHyphenated.bpmn2 | 89 ++++
.../CallActivityHyphenatedSubProcess.bpmn2 | 71 ++++
.../kogito/codegen/process/ProcessGenerator.java | 7 +-
.../process/bpmn2/StaticApplicationAssembler.java | 64 ---
.../canonical/LambdaSubProcessNodeVisitor.java | 203 +--------
.../class-templates/SubProcessFactoryTemplate.java | 31 --
.../core/factory/SubProcessNodeFactory.java | 7 -
.../jbpm/workflow/core/node/SubProcessFactory.java | 30 --
.../jbpm/workflow/core/node/SubProcessNode.java | 10 -
.../workflow/instance/impl/NodeInstanceImpl.java | 10 +
.../node/LambdaSubProcessNodeInstance.java | 95 ++++-
.../java/org/jbpm/process/test/NodeCreator.java | 4 +-
.../BPMN2-DynamicCallActivityByExpression.bpmn2 | 74 ++++
.../BPMN2-DynamicCallActivityByVariable.bpmn2 | 75 ++++
.../BPMN2-DynamicCallActivityMissingVariable.bpmn2 | 49 +++
.../BPMN2-DynamicCallActivityMultiExpression.bpmn2 | 77 ++++
.../BPMN2-DynamicCallActivityNoWait.bpmn2 | 55 +++
.../BPMN2-DynamicCallActivityScriptResolved.bpmn2 | 80 ++++
.../BPMN2-DynamicCallActivityTwoSequential.bpmn2 | 72 ++++
.../BPMN2-DynamicCallActivityUnknownProcess.bpmn2 | 51 +++
...PMN2-DynamicCallActivityWithBoundaryError.bpmn2 | 100 +++++
.../subprocess/BPMN2-DynamicSubProcessA.bpmn2 | 49 +++
.../subprocess/BPMN2-DynamicSubProcessB.bpmn2 | 49 +++
.../subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2 | 63 +++
.../java/org/jbpm/bpmn2/DynamicSubProcessTest.java | 468 +++++++++++++++++++++
27 files changed, 1552 insertions(+), 367 deletions(-)
diff --git
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
index 98faa042912..e06b5c9799f 100644
---
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
+++
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
@@ -216,4 +216,38 @@ public class CallActivityTaskIT extends AbstractCodegenIT {
.containsEntry("x", "a");
}
+ /**
+ * Verifies that a subprocess whose process id contains hyphens
+ */
+ @Test
+ public void testCallActivityWithHyphenatedSubProcessId() throws Exception {
+
+ Application app = generateCodeProcessesOnly(
+ "subprocess/CallActivityHyphenated.bpmn2",
+ "subprocess/CallActivityHyphenatedSubProcess.bpmn2");
+ assertThat(app).isNotNull();
+
+ // The hyphenated subprocess must be reachable by its original id
+
assertThat(app.get(Processes.class).processById("call-activity-sub-process")).isNotNull();
+
+ Process<? extends Model> p =
app.get(Processes.class).processById("ParentProcessHyphenated");
+ assertThat(p).isNotNull();
+
+ Model m = p.createModel();
+ Map<String, Object> parameters = new HashMap<>();
+ parameters.put("x", "inputValue");
+ parameters.put("y", "");
+ m.fromMap(parameters);
+
+ ProcessInstance<?> processInstance = p.createInstance(m);
+ processInstance.start();
+
+
assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
+ Model result = (Model) processInstance.variables();
+ // The child sets subY = "hyphenated result"; it is mapped back into
parent's y
+ assertThat(result.toMap())
+ .containsEntry("y", "hyphenated result")
+ .containsEntry("x", "inputValue");
+ }
+
}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
index b1cbbd61950..02249fcef4e 100644
---
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
+++
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
@@ -22,3 +22,5 @@ links/DifferentLinkProcess.bpmn2
links/EmptyLinkProcess.bpmn2
links/MultipleCatchLinkProcess.bpmn2
links/UnconnectedLinkProcess.bpmn2
+subprocess/CallActivityHyphenated.bpmn2
+subprocess/CallActivityHyphenatedSubProcess.bpmn2
diff --git
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenated.bpmn2
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenated.bpmn2
new file mode 100644
index 00000000000..796c14489e5
--- /dev/null
+++
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenated.bpmn2
@@ -0,0 +1,89 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/HyphenatedSubprocess"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_xItem" structureRef="String"/>
+ <itemDefinition id="_yItem" structureRef="String"/>
+
+ <!--
+ Parent process whose callActivity invokes a subprocess whose id contains
hyphens.
+ ProcessGenerator must sanitize the subprocess id when generating the Java
field
+ name (e.g. "call-activity-sub-process" ->
"processCall_activity_sub_process").
+ Without the fix (using sanitizeClassName) this would produce an invalid
Java
+ identifier and fail to compile.
+ -->
+ <process processType="Private" isExecutable="true"
+ id="ParentProcessHyphenated"
+ name="Parent Process Hyphenated">
+
+ <property id="x" itemSubjectRef="_xItem"/>
+ <property id="y" itemSubjectRef="_yItem"/>
+
+ <startEvent id="_1" name="StartProcess"/>
+ <callActivity id="_2" name="CallActivity"
calledElement="call-activity-sub-process">
+ <ioSpecification>
+ <dataInput id="_2_subXInput" tns:dtype="String" name="subX"/>
+ <dataOutput id="_2_subYOutput" tns:dtype="String" name="subY"/>
+ <inputSet>
+ <dataInputRefs>_2_subXInput</dataInputRefs>
+ </inputSet>
+ <outputSet>
+ <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+ </outputSet>
+ </ioSpecification>
+ <dataInputAssociation>
+ <sourceRef>x</sourceRef>
+ <targetRef>_2_subXInput</targetRef>
+ </dataInputAssociation>
+ <dataOutputAssociation>
+ <sourceRef>_2_subYOutput</sourceRef>
+ <targetRef>y</targetRef>
+ </dataOutputAssociation>
+ </callActivity>
+ <endEvent id="_3" name="EndProcess">
+ <terminateEventDefinition/>
+ </endEvent>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+ <bpmndi:BPMNDiagram>
+ <bpmndi:BPMNPlane bpmnElement="ParentProcessHyphenated">
+ <bpmndi:BPMNShape bpmnElement="_1"><dc:Bounds x="16" y="16" width="48"
height="48"/></bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="_2"><dc:Bounds x="96" y="16" width="110"
height="48"/></bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="_3"><dc:Bounds x="238" y="16" width="48"
height="48"/></bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge bpmnElement="_1-_2"><di:waypoint x="40"
y="40"/><di:waypoint x="151" y="40"/></bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="_2-_3"><di:waypoint x="151"
y="40"/><di:waypoint x="262" y="40"/></bpmndi:BPMNEdge>
+ </bpmndi:BPMNPlane>
+ </bpmndi:BPMNDiagram>
+
+</definitions>
diff --git
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenatedSubProcess.bpmn2
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenatedSubProcess.bpmn2
new file mode 100644
index 00000000000..797b481ec9c
--- /dev/null
+++
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenatedSubProcess.bpmn2
@@ -0,0 +1,71 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/HyphenatedSubprocess"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_subXItem" structureRef="String"/>
+ <itemDefinition id="_subYItem" structureRef="String"/>
+
+ <!--
+ Subprocess with a hyphenated process id: "call-activity-sub-process".
+ The hyphens must be handled by ProcessGenerator.sanitizeClassName() so the
+ generated Java field name is a valid identifier.
+ -->
+ <process processType="Private" isExecutable="true"
+ id="call-activity-sub-process"
+ name="Call Activity Sub Process"
+ tns:version="1">
+
+ <property id="subX" itemSubjectRef="_subXItem"/>
+ <property id="subY" itemSubjectRef="_subYItem"/>
+
+ <startEvent id="_1" name="StartProcess"/>
+ <scriptTask id="_2" name="SetOutput">
+ <script>kcontext.setVariable("subY", "hyphenated result");</script>
+ </scriptTask>
+ <endEvent id="_3" name="EndProcess">
+ <terminateEventDefinition/>
+ </endEvent>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+ <bpmndi:BPMNDiagram>
+ <bpmndi:BPMNPlane bpmnElement="call-activity-sub-process">
+ <bpmndi:BPMNShape bpmnElement="_1"><dc:Bounds x="16" y="16" width="48"
height="48"/></bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="_2"><dc:Bounds x="96" y="16" width="80"
height="48"/></bpmndi:BPMNShape>
+ <bpmndi:BPMNShape bpmnElement="_3"><dc:Bounds x="208" y="16" width="48"
height="48"/></bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge bpmnElement="_1-_2"><di:waypoint x="40"
y="40"/><di:waypoint x="136" y="40"/></bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge bpmnElement="_2-_3"><di:waypoint x="136"
y="40"/><di:waypoint x="232" y="40"/></bpmndi:BPMNEdge>
+ </bpmndi:BPMNPlane>
+ </bpmndi:BPMNDiagram>
+
+</definitions>
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
index 00eda479eb1..85ec8510478 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
@@ -491,11 +491,10 @@ public class ProcessGenerator {
for (Entry<String, String> subProcess :
processMetaData.getSubProcesses().entrySet()) {
FieldDeclaration subprocessFieldDeclaration = new
FieldDeclaration();
- String fieldName = "process" + subProcess.getKey();
+ String fieldName = "process" +
sanitizeClassName(subProcess.getKey());
+ String subProcessModelClass = packageName + "." +
sanitizeClassName(subProcess.getKey() + "Model");
ClassOrInterfaceType modelType = new
ClassOrInterfaceType(null, new
SimpleName(org.kie.kogito.process.Process.class.getCanonicalName()),
- NodeList.nodeList(
- new ClassOrInterfaceType(null,
processMetaData.getModelPackageName() != null ?
processMetaData.getModelPackageName() + "." +
processMetaData.getModelClassName()
- :
sanitizeClassName(subProcess.getKey() + "Model"))));
+ NodeList.nodeList(new ClassOrInterfaceType(null,
subProcessModelClass)));
if (context.hasDI()) {
subprocessFieldDeclaration
.addVariable(new VariableDeclarator(modelType,
fieldName));
diff --git
a/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
b/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
index 8241b89a33f..be2dc6b88fe 100644
---
a/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
+++
b/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
@@ -19,9 +19,7 @@
package org.kie.kogito.process.bpmn2;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.drools.io.ClassPathResource;
import org.jbpm.bpmn2.xml.BPMNDISemanticModule;
@@ -29,19 +27,12 @@ import org.jbpm.bpmn2.xml.BPMNExtensionsSemanticModule;
import org.jbpm.bpmn2.xml.BPMNSemanticModule;
import org.jbpm.compiler.xml.XmlProcessReader;
import org.jbpm.compiler.xml.core.SemanticModules;
-import org.jbpm.workflow.core.WorkflowProcess;
-import org.jbpm.workflow.core.impl.NodeIoHelper;
-import org.jbpm.workflow.core.node.SubProcessFactory;
-import org.jbpm.workflow.core.node.SubProcessNode;
-import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
import org.kie.api.definition.process.Process;
import org.kie.api.io.Resource;
-import org.kie.api.runtime.process.ProcessContext;
import org.kie.kogito.Application;
import org.kie.kogito.StaticApplication;
import org.kie.kogito.StaticConfig;
import org.kie.kogito.process.ProcessConfig;
-import org.kie.kogito.process.ProcessInstance;
import org.kie.kogito.process.ProcessInstancesFactory;
public class StaticApplicationAssembler {
@@ -96,26 +87,6 @@ public class StaticApplicationAssembler {
.map(process -> new BpmnProcess(process, processConfig,
application))
.forEach(container::addProcess);
- // Initialize SubProcessFactory for all CallActivity nodes
- container.processIds().forEach(processId -> {
- BpmnProcess bpmnProcess = (BpmnProcess)
container.processById(processId);
- WorkflowProcess workflowProcess = (WorkflowProcess)
bpmnProcess.process();
-
- workflowProcess.getNodesRecursively().forEach(node -> {
- if (node instanceof SubProcessNode) {
- SubProcessNode subProcessNode = (SubProcessNode) node;
- String subProcessId = subProcessNode.getProcessId();
-
- // Find the subprocess in the container
- org.kie.kogito.process.Process<?> subprocess =
container.processById(subProcessId);
- if (subprocess != null) {
- subProcessNode.setSubProcessFactory(
- new
BpmnSubProcessFactory((org.kie.kogito.process.Process<BpmnVariables>)
subprocess));
- }
- }
- });
- });
-
return application;
}
@@ -123,39 +94,4 @@ public class StaticApplicationAssembler {
return INSTANCE;
}
- /**
- * SubProcessFactory implementation for BPMN CallActivity nodes.
- * Handles parameter binding between parent and subprocess.
- */
- private static class BpmnSubProcessFactory implements
SubProcessFactory<BpmnVariables> {
-
- private final org.kie.kogito.process.Process<BpmnVariables> subprocess;
-
- BpmnSubProcessFactory(org.kie.kogito.process.Process<BpmnVariables>
subprocess) {
- this.subprocess = subprocess;
- }
-
- @Override
- public BpmnVariables bind(ProcessContext kcontext) {
- Map<String, Object> parameters = NodeIoHelper.processInputs(
- (NodeInstanceImpl) kcontext.getNodeInstance(),
- kcontext::getVariable);
- return BpmnVariables.create(parameters);
- }
-
- @Override
- public ProcessInstance<BpmnVariables> createInstance(BpmnVariables
model) {
- return subprocess.createInstance(model);
- }
-
- @Override
- public void unbind(ProcessContext kcontext, BpmnVariables model) {
- Map<String, Object> outputs = new HashMap<>(model.toMap());
- NodeIoHelper.processOutputs(
- (NodeInstanceImpl) kcontext.getNodeInstance(),
- outputs::get,
- kcontext::getVariable);
- }
- }
-
}
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
index f05c4f0591d..696b96088df 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
@@ -18,42 +18,14 @@
*/
package org.jbpm.compiler.canonical;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
+import org.jbpm.compiler.canonical.descriptors.ExpressionUtils;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.ruleflow.core.factory.SubProcessNodeFactory;
-import org.jbpm.workflow.core.impl.DataDefinition;
import org.jbpm.workflow.core.node.SubProcessNode;
-import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
-import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess;
-import org.kie.kogito.process.ProcessInstance;
-import org.kie.kogito.process.Processes;
-import com.github.javaparser.ast.NodeList;
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.body.Parameter;
-import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
-import com.github.javaparser.ast.expr.CastExpr;
-import com.github.javaparser.ast.expr.ClassExpr;
-import com.github.javaparser.ast.expr.Expression;
-import com.github.javaparser.ast.expr.LambdaExpr;
-import com.github.javaparser.ast.expr.MethodCallExpr;
-import com.github.javaparser.ast.expr.NameExpr;
-import com.github.javaparser.ast.expr.ObjectCreationExpr;
-import com.github.javaparser.ast.expr.StringLiteralExpr;
-import com.github.javaparser.ast.expr.VariableDeclarationExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
-import com.github.javaparser.ast.stmt.ReturnStmt;
-import com.github.javaparser.ast.type.ClassOrInterfaceType;
-import static com.github.javaparser.StaticJavaParser.parse;
-import static com.github.javaparser.ast.NodeList.nodeList;
-import static org.drools.util.StringUtils.ucFirst;
import static
org.jbpm.ruleflow.core.factory.SubProcessNodeFactory.METHOD_INDEPENDENT;
import static
org.jbpm.ruleflow.core.factory.SubProcessNodeFactory.METHOD_PROCESS_ID;
import static
org.jbpm.ruleflow.core.factory.SubProcessNodeFactory.METHOD_PROCESS_NAME;
@@ -72,12 +44,6 @@ public class LambdaSubProcessNodeVisitor extends
AbstractNodeVisitor<SubProcessN
@Override
public void visitNode(String factoryField, SubProcessNode node, BlockStmt
body, VariableScope variableScope, ProcessMetaData metadata) {
- Optional<Expression> retValue;
- try (InputStream resourceAsStream =
this.getClass().getResourceAsStream("/class-templates/SubProcessFactoryTemplate.java"))
{
- retValue = parse(resourceAsStream).findFirst(Expression.class);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
String name = node.getName();
String subProcessId = node.getProcessId();
@@ -87,175 +53,18 @@ public class LambdaSubProcessNodeVisitor extends
AbstractNodeVisitor<SubProcessN
body.addStatement(getAssignedFactoryMethod(factoryField,
SubProcessNodeFactory.class, getNodeId(node), getNodeKey(),
getWorkflowElementConstructor(node.getId())))
.addStatement(getNameMethod(node, "Call Activity"))
- .addStatement(getFactoryMethod(getNodeId(node),
METHOD_PROCESS_ID, new StringLiteralExpr(subProcessId)))
- .addStatement(getFactoryMethod(getNodeId(node),
METHOD_PROCESS_NAME, new StringLiteralExpr(getOrDefault(node.getProcessName(),
""))))
+ .addStatement(getFactoryMethod(getNodeId(node),
METHOD_PROCESS_ID, ExpressionUtils.getLiteralExpr(subProcessId)))
+ .addStatement(getFactoryMethod(getNodeId(node),
METHOD_PROCESS_NAME,
ExpressionUtils.getLiteralExpr(getOrDefault(node.getProcessName(), ""))))
.addStatement(getFactoryMethod(getNodeId(node),
METHOD_WAIT_FOR_COMPLETION, new BooleanLiteralExpr(node.isWaitForCompletion())))
.addStatement(getFactoryMethod(getNodeId(node),
METHOD_INDEPENDENT, new BooleanLiteralExpr(node.isIndependent())));
- Map<String, String> inputTypes =
node.getIoSpecification().getInputTypes();
-
- String subProcessModelClassName = metadata.getModelClassName() != null
? metadata.getModelClassName() :
ProcessToExecModelGenerator.extractModelClassName(subProcessId);
-
- ModelMetaData subProcessModel = new ModelMetaData(subProcessId,
- metadata.getModelPackageName() != null ?
metadata.getModelPackageName() : metadata.getPackageName(),
- subProcessModelClassName,
- KogitoWorkflowProcess.PRIVATE_VISIBILITY,
- VariableDeclarations.ofRawInfo(inputTypes),
- false);
-
- retValue.ifPresentOrElse(retValueExpression -> {
-
- retValueExpression.findAll(ClassOrInterfaceType.class)
- .stream()
- .filter(t -> t.getNameAsString().equals("$Type$"))
- .forEach(t -> t.setName(subProcessModelClassName));
-
- retValueExpression.findFirst(MethodDeclaration.class, m ->
m.getNameAsString().equals("bind"))
- .ifPresent(m -> m.setBody(bind(subProcessModel)));
- retValueExpression.findFirst(MethodDeclaration.class, m ->
m.getNameAsString().equals("createInstance"))
- .ifPresent(m -> m.setBody(createInstance(node, metadata)));
- retValueExpression.findFirst(MethodDeclaration.class, m ->
m.getNameAsString().equals("unbind"))
- .ifPresent(m -> m.setBody(unbind(node)));
- body.addStatement(getFactoryMethod(getNodeId(node), getNodeKey(),
retValueExpression));
-
- }, () -> body.addStatement(getFactoryMethod(getNodeId(node),
getNodeKey())));
-
addNodeMappings(node, body, getNodeId(node));
visitMetaData(node.getMetaData(), body, getNodeId(node));
body.addStatement(getDoneMethod(getNodeId(node)));
- }
-
- private BlockStmt bind(ModelMetaData subProcessModel) {
- BlockStmt actionBody = new BlockStmt();
- actionBody.addStatement(subProcessModel.newInstance("model"));
-
- // process the inputs of the task
- ClassOrInterfaceType nodeInstanceType = new ClassOrInterfaceType(null,
NodeInstanceImpl.class.getCanonicalName());
- ClassOrInterfaceType objectType = new ClassOrInterfaceType(null,
Object.class.getCanonicalName());
- ClassOrInterfaceType stringType = new ClassOrInterfaceType(null,
String.class.getCanonicalName());
- ClassOrInterfaceType type = new ClassOrInterfaceType(null,
Map.class.getCanonicalName()).setTypeArguments(nodeList(stringType,
objectType));
- VariableDeclarationExpr expr = new VariableDeclarationExpr(type,
"inputs");
-
- BlockStmt lambdaBody = new BlockStmt();
- MethodCallExpr getVariableExpr = new MethodCallExpr(new
NameExpr(KCONTEXT_VAR), "getVariable").addArgument(new NameExpr("name"));
- Expression getNodeInstance = new CastExpr(nodeInstanceType, new
MethodCallExpr(new NameExpr(KCONTEXT_VAR), "getNodeInstance"));
- lambdaBody.addStatement(new ReturnStmt(getVariableExpr));
- Parameter varName = new Parameter(stringType, "name");
- LambdaExpr sourceResolverExpr = new LambdaExpr(nodeList(varName),
lambdaBody);
-
- MethodCallExpr processInputsExpr = new MethodCallExpr(null,
"org.jbpm.workflow.core.impl.NodeIoHelper.processInputs",
nodeList(getNodeInstance, sourceResolverExpr));
- AssignExpr inputs = new AssignExpr(expr, processInputsExpr,
AssignExpr.Operator.ASSIGN);
- actionBody.addStatement(inputs);
-
- actionBody.addStatement(subProcessModel.callUpdateFromMap("model",
"inputs"));
- actionBody.addStatement(new ReturnStmt(new NameExpr("model")));
- return actionBody;
- }
-
- private BlockStmt createInstance(SubProcessNode subProcessNode,
ProcessMetaData metadata) {
- String processId =
ProcessToExecModelGenerator.extractProcessId(subProcessNode.getProcessId());
- String subProcessModelClassName = metadata.getModelClassName() != null
? metadata.getModelClassName() :
ProcessToExecModelGenerator.extractModelClassName(processId);
- String processFieldName = "app";
- Expression expr = new NameExpr(processFieldName);
- ClassOrInterfaceType processesType = new ClassOrInterfaceType(null,
Processes.class.getCanonicalName());
- expr = new MethodCallExpr(expr, "get", NodeList.nodeList(new
ClassExpr(processesType)));
- expr = new MethodCallExpr(expr, "processById", NodeList.nodeList(new
StringLiteralExpr(subProcessNode.getProcessId())));
- expr = new MethodCallExpr(expr, "createInstance").addArgument("model");
- ClassOrInterfaceType subProcessType = new ClassOrInterfaceType(null,
ProcessInstance.class.getCanonicalName());
- subProcessType.setTypeArguments(new ClassOrInterfaceType(null,
subProcessModelClassName));
- expr = new CastExpr(subProcessType, expr);
-
- metadata.addSubProcess(processId, subProcessNode.getProcessId());
-
- return new BlockStmt().addStatement(new ReturnStmt(expr));
- }
- private BlockStmt unbind(SubProcessNode subProcessNode) {
- BlockStmt actionBody = new BlockStmt();
-
- // process the outputs of the task
- ClassOrInterfaceType nodeInstanceType = new ClassOrInterfaceType(null,
NodeInstanceImpl.class.getCanonicalName());
- ClassOrInterfaceType objectType = new ClassOrInterfaceType(null,
Object.class.getCanonicalName());
- ClassOrInterfaceType stringType = new ClassOrInterfaceType(null,
String.class.getCanonicalName());
- ClassOrInterfaceType type = new ClassOrInterfaceType(null,
Map.class.getCanonicalName()).setTypeArguments(nodeList(stringType,
objectType));
- ClassOrInterfaceType hashMapType = new ClassOrInterfaceType(null,
HashMap.class.getCanonicalName()).setTypeArguments(nodeList(stringType,
objectType));
-
- // we get the outputs from model
- VariableDeclarationExpr expr = new VariableDeclarationExpr(type,
"outputs");
- actionBody.addStatement(new AssignExpr(expr, new
ObjectCreationExpr(null, hashMapType, nodeList()), AssignExpr.Operator.ASSIGN));
- // do the actual assignments
- for (DataDefinition outputDefinition :
subProcessNode.getIoSpecification().getDataOutput().values()) {
- // remove multiinstance data. It does not belong to this model it
is just for calculations with
- // data associations
- String collectionOutput = (String)
subProcessNode.getMetaData().get("MICollectionOutput");
- if (collectionOutput != null &&
collectionOutput.equals(outputDefinition.getLabel())) {
- continue;
- }
- DataDefinition multiInstance =
subProcessNode.getMultiInstanceSpecification().getOutputDataItem();
- if (multiInstance != null &&
multiInstance.getLabel().equals(outputDefinition.getLabel())) {
- continue;
- }
-
- Expression getValueExpr = new MethodCallExpr(new
NameExpr("model"), "get" + ucFirst(outputDefinition.getLabel()));
- Expression setValueExpr = new MethodCallExpr(new
NameExpr("outputs"), "put", nodeList(new
StringLiteralExpr(outputDefinition.getLabel()), getValueExpr));
- actionBody.addStatement(setValueExpr);
- }
-
- // source resolver
- BlockStmt lambdaSourceBody = new BlockStmt();
- Expression getSourceVariableExpr = new MethodCallExpr(new
NameExpr("outputs"), "get", nodeList(new NameExpr("name")));
- lambdaSourceBody.addStatement(new ReturnStmt(getSourceVariableExpr));
- LambdaExpr sourceResolverExpr = new LambdaExpr(nodeList(new
Parameter(stringType, "name")), lambdaSourceBody);
-
- // target resolver
- BlockStmt lambdaTargetBody = new BlockStmt();
- Expression getTargetVariableExpr = new MethodCallExpr(new
NameExpr(KCONTEXT_VAR), "getVariable").addArgument(new NameExpr("name"));
- lambdaTargetBody.addStatement(new ReturnStmt(getTargetVariableExpr));
- LambdaExpr targetResolverExpr = new LambdaExpr(nodeList(new
Parameter(stringType, "name")), lambdaTargetBody);
-
- Expression getNodeInstance = new CastExpr(nodeInstanceType, new
MethodCallExpr(new NameExpr(KCONTEXT_VAR), "getNodeInstance"));
- MethodCallExpr processOutputsExpr = new MethodCallExpr(null,
"org.jbpm.workflow.core.impl.NodeIoHelper.processOutputs",
nodeList(getNodeInstance, sourceResolverExpr, targetResolverExpr));
-
- actionBody.addStatement(processOutputsExpr);
-
- return actionBody;
- }
-
- protected Expression dotNotationToSetExpression(String dotNotation, String
value) {
- String[] elements = dotNotation.split("\\.");
- Expression scope = new NameExpr(elements[0]);
- if (elements.length == 1) {
- return new AssignExpr(
- scope,
- new NameExpr(value),
- AssignExpr.Operator.ASSIGN);
- }
- for (int i = 1; i < elements.length - 1; i++) {
- scope = new MethodCallExpr()
- .setScope(scope)
- .setName("get" + ucFirst(elements[i]));
- }
-
- return new MethodCallExpr()
- .setScope(scope)
- .setName("set" + ucFirst(elements[elements.length - 1]))
- .addArgument(value);
- }
-
- protected Expression dotNotationToGetExpression(String dotNotation) {
- String[] elements = dotNotation.split("\\.");
- Expression scope = new NameExpr(elements[0]);
-
- if (elements.length == 1) {
- return scope;
+ if (subProcessId != null && !subProcessId.contains("#{")) {
+ String processId =
ProcessToExecModelGenerator.extractProcessId(subProcessId);
+ metadata.addSubProcess(processId, subProcessId);
}
-
- for (int i = 1; i < elements.length; i++) {
- scope = new MethodCallExpr()
- .setScope(scope)
- .setName("get" + ucFirst(elements[i]));
- }
-
- return scope;
}
}
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/SubProcessFactoryTemplate.java
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/SubProcessFactoryTemplate.java
deleted file mode 100644
index ff1eebc3cbd..00000000000
---
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/SubProcessFactoryTemplate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-class Template {
- Object f = new org.jbpm.workflow.core.node.SubProcessFactory<$Type$>() {
- public $Type$ bind(org.kie.api.runtime.process.ProcessContext
kcontext) {
- return null;
- }
- public org.kie.kogito.process.ProcessInstance<$Type$>
createInstance($Type$ model) {
- return null;
- }
- public void unbind(org.kie.api.runtime.process.ProcessContext
kcontext, $Type$ model) {
-
- }
- };
-}
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
index 574b8e498d2..5f5e806010b 100755
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
+++
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
@@ -22,10 +22,8 @@ import org.jbpm.process.core.ContextContainer;
import org.jbpm.process.core.context.variable.Mappable;
import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory;
import org.jbpm.workflow.core.NodeContainer;
-import org.jbpm.workflow.core.node.SubProcessFactory;
import org.jbpm.workflow.core.node.SubProcessNode;
import org.kie.api.definition.process.WorkflowElementIdentifier;
-import org.kie.kogito.Model;
public class SubProcessNodeFactory<T extends RuleFlowNodeContainerFactory<T,
?>> extends StateBasedNodeFactory<SubProcessNodeFactory<T>, T>
implements MappableNodeFactory<SubProcessNodeFactory<T>>,
ContextContainerFactory<SubProcessNodeFactory<T>> {
@@ -68,11 +66,6 @@ public class SubProcessNodeFactory<T extends
RuleFlowNodeContainerFactory<T, ?>>
return this;
}
- public SubProcessNodeFactory<T> subProcessNode(SubProcessFactory<? extends
Model> factory) {
- getSubProcessNode().setSubProcessFactory(factory);
- return this;
- }
-
@Override
public ContextContainer getContextNode() {
return getSubProcessNode();
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessFactory.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessFactory.java
deleted file mode 100644
index 789ca166505..00000000000
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessFactory.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.jbpm.workflow.core.node;
-
-import org.kie.api.runtime.process.ProcessContext;
-import org.kie.kogito.process.ProcessInstance;
-
-public interface SubProcessFactory<T> {
- T bind(ProcessContext ctx);
-
- ProcessInstance<T> createInstance(T model);
-
- void unbind(ProcessContext ctx, T model);
-}
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
index d3711219a4f..ef13ee68eba 100755
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
+++
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
@@ -46,7 +46,6 @@ public class SubProcessNode extends StateBasedNode implements
ContextContainer {
private boolean waitForCompletion = true;
private boolean independent = true;
- private SubProcessFactory<?> subProcessFactory;
public SubProcessNode() {
super(NodeType.SUBPROCESS);
@@ -153,13 +152,4 @@ public class SubProcessNode extends StateBasedNode
implements ContextContainer {
}
return Boolean.parseBoolean(abortParent);
}
-
- public <T> void setSubProcessFactory(
- SubProcessFactory<T> subProcessFactory) {
- this.subProcessFactory = subProcessFactory;
- }
-
- public SubProcessFactory<?> getSubProcessFactory() {
- return subProcessFactory;
- }
}
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
index 2dc6622ab32..fdb4dfd9d1a 100755
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
+++
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
@@ -711,6 +711,16 @@ public abstract class NodeInstanceImpl implements
org.jbpm.workflow.instance.Nod
return expression != null &&
PatternConstants.PARAMETER_MATCHER.matcher(expression).find();
}
+ /**
+ * Extracts the first variable/expression name from a #{...} template.
+ * For example, {@code "#{processName}"} returns {@code "processName"} and
+ * {@code "#{a + b}-suffix"} returns {@code "a + b"}.
+ */
+ protected static String extractFirstVariableName(String expression) {
+ Matcher m = PatternConstants.PARAMETER_MATCHER.matcher(expression);
+ return m.find() ? m.group(1) : expression;
+ }
+
public String resolveExpression(String expression) {
return isExpression(expression) ? (String) resolveValue(expression) :
expression;
}
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
index f6851885d26..7220d0c72c6 100755
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
+++
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
@@ -38,9 +38,8 @@ import org.jbpm.process.instance.impl.ContextInstanceFactory;
import org.jbpm.process.instance.impl.ContextInstanceFactoryRegistry;
import org.jbpm.process.instance.impl.ProcessInstanceImpl;
import org.jbpm.ruleflow.core.Metadata;
-import org.jbpm.util.ContextFactory;
import org.jbpm.workflow.core.Node;
-import org.jbpm.workflow.core.node.SubProcessFactory;
+import org.jbpm.workflow.core.impl.NodeIoHelper;
import org.jbpm.workflow.core.node.SubProcessNode;
import org.kie.kogito.Model;
import org.kie.kogito.internal.process.event.KogitoEventListener;
@@ -48,6 +47,7 @@ import
org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
import org.kie.kogito.process.MutableProcessInstances;
+import org.kie.kogito.process.Process;
import org.kie.kogito.process.Processes;
import org.kie.kogito.process.impl.AbstractProcessInstance;
import org.slf4j.Logger;
@@ -84,12 +84,22 @@ public class LambdaSubProcessNodeInstance extends
StateBasedNodeInstance impleme
"A SubProcess node only accepts default incoming
connections!");
}
- KogitoProcessContextImpl context = ContextFactory.fromNode(this);
- SubProcessFactory subProcessFactory =
getSubProcessNode().getSubProcessFactory();
- Object o = subProcessFactory.bind(context);
- org.kie.kogito.process.ProcessInstance<?> processInstance =
subProcessFactory.createInstance(o);
+ String processId = resolveProcessId();
+ ProcessInstance processInstance = (ProcessInstance)
getProcessInstance();
+ KogitoProcessRuntime kruntime = (KogitoProcessRuntime)
processInstance.getKnowledgeRuntime();
+ Process process =
kruntime.getApplication().get(Processes.class).processById(processId);
- ProcessInstanceImpl pi = (ProcessInstanceImpl)
((AbstractProcessInstance<?>) processInstance).internalGetProcessInstance();
+ if (process == null) {
+ throw new IllegalArgumentException("Cannot find process with id "
+ processId);
+ }
+
+ Map<String, Object> parameters = NodeIoHelper.processInputs(this, key
-> getVariable(key));
+
+ Model model = (Model) process.createModel();
+ model.update(parameters);
+ org.kie.kogito.process.ProcessInstance<?> subProcessInstance =
process.createInstance(model);
+
+ ProcessInstanceImpl pi = (ProcessInstanceImpl)
((AbstractProcessInstance<?>) subProcessInstance).internalGetProcessInstance();
pi.setMetaData("ParentProcessInstanceId",
getProcessInstance().getStringId());
pi.setMetaData("ParentNodeInstanceId", getUniqueId());
pi.setMetaData("ParentNodeId", getSubProcessNode().getUniqueId());
@@ -103,18 +113,22 @@ public class LambdaSubProcessNodeInstance extends
StateBasedNodeInstance impleme
logger.debug("Parent headers are {}", headers != null ?
headers.keySet() : Set.of());
}
pi.setHeaders(headers);
- // headers parameters set to null so start does not override the ones
already set
- processInstance.start(null);
- this.processInstanceId = processInstance.id();
- this.asyncWaitingNodeInstance = hasAsyncNodeInstance(pi);
- subProcessFactory.unbind(context, processInstance.variables());
+ subProcessInstance.start();
+
+ this.processInstanceId = subProcessInstance.id();
+ this.asyncWaitingNodeInstance = hasAsyncNodeInstance(pi);
if (!getSubProcessNode().isWaitForCompletion()) {
triggerCompleted();
- } else if (processInstance.status() ==
KogitoProcessInstance.STATE_COMPLETED || processInstance.status() ==
KogitoProcessInstance.STATE_ABORTED) {
+ } else if (subProcessInstance.status() ==
KogitoProcessInstance.STATE_COMPLETED || subProcessInstance.status() ==
KogitoProcessInstance.STATE_ABORTED) {
+ // Subprocess completed synchronously - handle output mappings
immediately
+ Map<String, Object> outputSet = new HashMap<>(((Model)
subProcessInstance.variables()).toMap());
+ NodeIoHelper.processOutputs(this, varRef -> outputSet.get(varRef),
varName -> this.getVariable(varName));
+
processInstanceCompleted((ProcessInstance) pi);
} else {
+
addProcessListener();
}
}
@@ -139,8 +153,9 @@ public class LambdaSubProcessNodeInstance extends
StateBasedNodeInstance impleme
}
KogitoProcessRuntime kruntime = (KogitoProcessRuntime)
((ProcessInstance) getProcessInstance()).getKnowledgeRuntime();
+ String processId = resolveProcessId();
Optional<AbstractProcessInstance> pi =
- ((MutableProcessInstances)
kruntime.getApplication().get(Processes.class).processById(this.getSubProcessNode().getProcessId()).instances()).findById(processInstanceId);
+ ((MutableProcessInstances)
kruntime.getApplication().get(Processes.class).processById(processId).instances()).findById(processInstanceId);
pi.ifPresent(e -> e.abort());
}
@@ -192,7 +207,6 @@ public class LambdaSubProcessNodeInstance extends
StateBasedNodeInstance impleme
processInstanceId = null;
if (processInstance.getState() == KogitoProcessInstance.STATE_ABORTED)
{
String faultName = processInstance.getOutcome() == null ? "" :
processInstance.getOutcome();
- // handle exception as sub process failed with error code
ExceptionScopeInstance exceptionScopeInstance =
(ExceptionScopeInstance) resolveContextInstance(ExceptionScope.EXCEPTION_SCOPE,
faultName);
if (exceptionScopeInstance != null) {
KogitoProcessContextImpl context = new
KogitoProcessContextImpl(this.getProcessInstance().getKnowledgeRuntime());
@@ -222,13 +236,8 @@ public class LambdaSubProcessNodeInstance extends
StateBasedNodeInstance impleme
@SuppressWarnings({ "unchecked", "rawtypes" })
private void handleOutMappings(ProcessInstance processInstance) {
- SubProcessFactory subProcessFactory =
getSubProcessNode().getSubProcessFactory();
- org.kie.kogito.process.ProcessInstance<?> pi =
((org.kie.kogito.process.ProcessInstance<?>) processInstance.unwrap());
- if (pi != null) {
- Model model = (Model) pi.process().createModel();
- model.fromMap(processInstance.getVariables());
- subProcessFactory.unbind(ContextFactory.fromNode(this), model);
- }
+ Map<String, Object> outputSet = processInstance.getVariables();
+ NodeIoHelper.processOutputs(this, varRef -> outputSet.get(varRef),
varName -> this.getVariable(varName));
}
@Override
@@ -294,4 +303,46 @@ public class LambdaSubProcessNodeInstance extends
StateBasedNodeInstance impleme
return getSubProcessNode();
}
+ /**
+ * Resolves the process ID, supporting variable substitution and MVEL
expressions.
+ *
+ * Leverages the inherited {@link
NodeInstanceImpl#resolveExpression(String)} method
+ * which supports multiple {@code #{...}} tokens in one string, e.g.:
+ * <ul>
+ * <li>Static process IDs: {@code "order-subprocess"}</li>
+ * <li>Simple variables: {@code "#{processName}"}</li>
+ * <li>Property access: {@code "#{order.type}"}</li>
+ * <li>Multiple expressions: {@code "#{prefix}-#{suffix}"}</li>
+ * <li>Mixed literals and expressions: {@code "#{order.type}-process"}</li>
+ * </ul>
+ *
+ * @return the resolved process ID
+ * @throws IllegalArgumentException if the expression contains
unresolvable variables
+ * @see NodeInstanceImpl#resolveExpression(String)
+ */
+ private String resolveProcessId() {
+ String processId = getSubProcessNode().getProcessId();
+
+ if (processId == null) {
+ return null;
+ }
+
+ String resolvedId = resolveExpression(processId);
+
+ if (isExpression(processId)) {
+ if (resolvedId == null || resolvedId.trim().isEmpty() ||
resolvedId.contains("#{")) {
+ throw new IllegalArgumentException(
+ "Cannot resolve subprocess ID from expression '" +
processId + "': "
+ + "Variable '" +
extractFirstVariableName(processId)
+ + "' not found in process context or is
empty");
+ }
+ }
+
+ if (logger.isDebugEnabled() && !processId.equals(resolvedId)) {
+ logger.debug("Resolved process ID from '{}' to '{}'", processId,
resolvedId);
+ }
+
+ return resolvedId;
+ }
+
}
diff --git
a/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
b/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
index 6233e97abb8..d83deeeac23 100755
--- a/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
+++ b/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
@@ -38,9 +38,9 @@ public class NodeCreator<T extends NodeImpl> {
public NodeCreator(NodeContainer nodeContainer, Class<T> clazz) {
this.nodeContainer = nodeContainer;
try {
- this.constructor = clazz.getConstructor();
+ this.constructor = (Constructor<T>) clazz.getConstructor();
} catch (NoSuchMethodException e) {
- throw new IllegalArgumentException("No public no-arg constructor
found on " + clazz.getName(), e);
+ throw new RuntimeException("No public no-arg constructor found for
" + clazz.getName(), e);
}
}
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2
new file mode 100644
index 00000000000..56cc10c3ee5
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2
@@ -0,0 +1,74 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_prefixItem" structureRef="String"/>
+ <itemDefinition id="_xItem" structureRef="String"/>
+ <itemDefinition id="_yItem" structureRef="String"/>
+
+ <!-- calledElement is a MVEL expression: prefix + literal suffix -->
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityByExpression"
+ name="Dynamic Call Activity By Expression"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <property id="prefix" itemSubjectRef="_prefixItem"/>
+ <property id="x" itemSubjectRef="_xItem"/>
+ <property id="y" itemSubjectRef="_yItem"/>
+
+ <startEvent id="_1" name="Start"/>
+ <callActivity id="_2" name="DynamicSubProcess" calledElement="#{prefix +
'SubProcess'}">
+ <ioSpecification>
+ <dataInput id="_2_subXInput" name="subX"/>
+ <dataOutput id="_2_subYOutput" name="subY"/>
+ <inputSet>
+ <dataInputRefs>_2_subXInput</dataInputRefs>
+ </inputSet>
+ <outputSet>
+ <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+ </outputSet>
+ </ioSpecification>
+ <dataInputAssociation>
+ <sourceRef>x</sourceRef>
+ <targetRef>_2_subXInput</targetRef>
+ </dataInputAssociation>
+ <dataOutputAssociation>
+ <sourceRef>_2_subYOutput</sourceRef>
+ <targetRef>y</targetRef>
+ </dataOutputAssociation>
+ </callActivity>
+ <endEvent id="_3" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2
new file mode 100644
index 00000000000..3e4db04b29f
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2
@@ -0,0 +1,75 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+ <itemDefinition id="_xItem" structureRef="String"/>
+ <itemDefinition id="_yItem" structureRef="String"/>
+
+ <!-- Parent process: calledElement is a variable expression resolved at
runtime -->
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityByVariable"
+ name="Dynamic Call Activity By Variable"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+ <property id="x" itemSubjectRef="_xItem"/>
+ <property id="y" itemSubjectRef="_yItem"/>
+
+ <startEvent id="_1" name="Start"/>
+ <callActivity id="_2" name="DynamicSubProcess"
calledElement="#{subprocessId}">
+ <ioSpecification>
+ <dataInput id="_2_subXInput" name="subX"/>
+ <dataOutput id="_2_subYOutput" name="subY"/>
+ <inputSet>
+ <dataInputRefs>_2_subXInput</dataInputRefs>
+ </inputSet>
+ <outputSet>
+ <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+ </outputSet>
+ </ioSpecification>
+ <dataInputAssociation>
+ <sourceRef>x</sourceRef>
+ <targetRef>_2_subXInput</targetRef>
+ </dataInputAssociation>
+ <dataOutputAssociation>
+ <sourceRef>_2_subYOutput</sourceRef>
+ <targetRef>y</targetRef>
+ </dataOutputAssociation>
+ </callActivity>
+ <endEvent id="_3" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2
new file mode 100644
index 00000000000..1fb3e97c125
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2
@@ -0,0 +1,49 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <!-- calledElement references a variable that is never declared as a process
variable -->
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityMissingVariable"
+ name="Dynamic Call Activity Missing Variable"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <!-- NOTE: no <property> for 'missingVar' — it is intentionally absent -->
+
+ <startEvent id="_1" name="Start"/>
+ <callActivity id="_2" name="DynamicSubProcessMissing"
calledElement="#{missingVar}"/>
+ <endEvent id="_3" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2
new file mode 100644
index 00000000000..e5f974d5abc
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2
@@ -0,0 +1,77 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_prefixItem" structureRef="String"/>
+ <itemDefinition id="_suffixItem" structureRef="String"/>
+ <itemDefinition id="_xItem" structureRef="String"/>
+ <itemDefinition id="_yItem" structureRef="String"/>
+
+ <!-- calledElement composed from two independent variable expressions:
+ "#{prefix}#{suffix}" e.g. "CallActivity" + "SubProcess" =
"CallActivitySubProcess" -->
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityMultiExpression"
+ name="Dynamic Call Activity Multi Expression"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <property id="prefix" itemSubjectRef="_prefixItem"/>
+ <property id="suffix" itemSubjectRef="_suffixItem"/>
+ <property id="x" itemSubjectRef="_xItem"/>
+ <property id="y" itemSubjectRef="_yItem"/>
+
+ <startEvent id="_1" name="Start"/>
+ <callActivity id="_2" name="DynamicSubProcess"
calledElement="#{prefix}#{suffix}">
+ <ioSpecification>
+ <dataInput id="_2_subXInput" name="subX"/>
+ <dataOutput id="_2_subYOutput" name="subY"/>
+ <inputSet>
+ <dataInputRefs>_2_subXInput</dataInputRefs>
+ </inputSet>
+ <outputSet>
+ <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+ </outputSet>
+ </ioSpecification>
+ <dataInputAssociation>
+ <sourceRef>x</sourceRef>
+ <targetRef>_2_subXInput</targetRef>
+ </dataInputAssociation>
+ <dataOutputAssociation>
+ <sourceRef>_2_subYOutput</sourceRef>
+ <targetRef>y</targetRef>
+ </dataOutputAssociation>
+ </callActivity>
+ <endEvent id="_3" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2
new file mode 100644
index 00000000000..2c278515c99
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2
@@ -0,0 +1,55 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+
+ <!-- waitForCompletion=false: parent completes without waiting for child -->
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityNoWait"
+ name="Dynamic Call Activity No Wait"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+
+ <startEvent id="_1" name="Start"/>
+ <callActivity id="_2" name="DynamicSubProcessNoWait"
+ calledElement="#{subprocessId}"
+ tns:waitForCompletion="false"
+ tns:independent="true">
+ </callActivity>
+ <endEvent id="_3" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2
new file mode 100644
index 00000000000..e932d0eea61
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2
@@ -0,0 +1,80 @@
+<?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.
+ -->
+
+<!--
+ Parent process where a script task computes the subprocess ID at runtime
+ and stores it in a variable. The callActivity then resolves #{subprocessId}
+ from that script-assigned value. Tests that variable resolution sees values
+ written during the running instance, not just start-time parameters.
+-->
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+ <itemDefinition id="_yItem" structureRef="String"/>
+
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityScriptResolved"
+ name="Dynamic Call Activity Script Resolved"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <!-- subprocessId starts null; the script task sets it before the
callActivity -->
+ <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+ <property id="y" itemSubjectRef="_yItem"/>
+
+ <startEvent id="_1" name="Start"/>
+
+ <!-- Script sets the subprocess ID programmatically -->
+ <scriptTask id="_2" name="ComputeSubprocessId">
+ <script>kcontext.setVariable("subprocessId",
"CallActivitySubProcess");</script>
+ </scriptTask>
+
+ <callActivity id="_3" name="DynamicSubProcess"
calledElement="#{subprocessId}"
+ tns:waitForCompletion="true" tns:independent="false">
+ <ioSpecification>
+ <dataOutput id="_3_subYOutput" name="subY"/>
+ <outputSet>
+ <dataOutputRefs>_3_subYOutput</dataOutputRefs>
+ </outputSet>
+ </ioSpecification>
+ <dataOutputAssociation>
+ <sourceRef>_3_subYOutput</sourceRef>
+ <targetRef>y</targetRef>
+ </dataOutputAssociation>
+ </callActivity>
+
+ <endEvent id="_4" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ <sequenceFlow id="_3-_4" sourceRef="_3" targetRef="_4"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2
new file mode 100644
index 00000000000..88e0de03f71
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2
@@ -0,0 +1,72 @@
+<?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.
+ -->
+
+<!--
+ Parent process with two sequential callActivity nodes.
+ Both use the same variable expression #{subprocessId}, but a script task
+ between them changes the value so each invocation resolves to a different
+ child process. This tests that resolution is truly per-execution, not cached.
+-->
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityTwoSequential"
+ name="Dynamic Call Activity Two Sequential"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+
+ <startEvent id="_1" name="Start"/>
+
+ <!-- First call — resolves subprocessId to whatever was passed at start -->
+ <callActivity id="_2" name="FirstCall" calledElement="#{subprocessId}"
+ tns:waitForCompletion="true" tns:independent="false"/>
+
+ <!-- Script switches the variable to the second child process -->
+ <scriptTask id="_3" name="SwitchSubprocess">
+ <script>kcontext.setVariable("subprocessId",
"DynamicSubProcessB");</script>
+ </scriptTask>
+
+ <!-- Second call — resolves subprocessId to "DynamicSubProcessB" -->
+ <callActivity id="_4" name="SecondCall" calledElement="#{subprocessId}"
+ tns:waitForCompletion="true" tns:independent="false"/>
+
+ <endEvent id="_5" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ <sequenceFlow id="_3-_4" sourceRef="_3" targetRef="_4"/>
+ <sequenceFlow id="_4-_5" sourceRef="_4" targetRef="_5"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2
new file mode 100644
index 00000000000..7d2dfdea56c
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2
@@ -0,0 +1,51 @@
+<?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.
+ -->
+
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ 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:tns="http://www.jboss.org/drools">
+
+ <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+
+ <!-- calledElement resolves to a non-existent process ID -->
+ <process processType="Private" isExecutable="true"
+ id="DynamicCallActivityUnknownProcess"
+ name="Dynamic Call Activity Unknown Process"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+
+ <startEvent id="_1" name="Start"/>
+ <callActivity id="_2" name="DynamicSubProcessUnknown"
calledElement="#{subprocessId}"/>
+ <endEvent id="_3" name="End"/>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityWithBoundaryError.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityWithBoundaryError.bpmn2
new file mode 100644
index 00000000000..0d757f162b6
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityWithBoundaryError.bpmn2
@@ -0,0 +1,100 @@
+<?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.
+ -->
+
+<!--
+ Parent process that calls a dynamically-resolved subprocess.
+ An Error Boundary Event (errorCode="subprocess-fault") is attached to the
+ callActivity. When the called subprocess ends with an error end event
+ carrying the same errorCode, the boundary event fires and the process
+ routes to the error-handling script and end event instead of the normal end.
+-->
+<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:tns="http://www.jboss.org/drools"
+ xmlns="http://www.jboss.org/drools"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd
http://www.jboss.org/drools drools.xsd"
+ id="Definition"
+ expressionLanguage="http://www.mvel.org/2.0"
+ targetNamespace="http://www.jboss.org/drools"
+ typeLanguage="http://www.java.com/javaTypes">
+
+ <!-- Error thrown by DynamicSubProcessFaulty -->
+ <bpmn2:error id="_subprocessFaultError" errorCode="subprocess-fault"
name="Subprocess Fault"/>
+
+ <bpmn2:itemDefinition id="_subprocessIdItem" structureRef="String"/>
+ <bpmn2:itemDefinition id="_resultItem" structureRef="String"/>
+
+ <bpmn2:process id="DynamicCallActivityWithBoundaryError"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess"
+ name="Dynamic Call Activity With Boundary Error"
+ isExecutable="true"
+ processType="Private">
+
+ <bpmn2:property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+ <!-- result is written by the error-handling script so the test can assert
it -->
+ <bpmn2:property id="result" itemSubjectRef="_resultItem"/>
+
+ <bpmn2:startEvent id="_1" name="Start">
+ <bpmn2:outgoing>_1-_2</bpmn2:outgoing>
+ </bpmn2:startEvent>
+
+ <bpmn2:callActivity id="_2" name="DynamicSubProcess"
+ calledElement="#{subprocessId}"
+ tns:waitForCompletion="true"
+ tns:independent="false">
+ <bpmn2:incoming>_1-_2</bpmn2:incoming>
+ <bpmn2:outgoing>_2-_3</bpmn2:outgoing>
+ </bpmn2:callActivity>
+
+ <!-- normal completion end -->
+ <bpmn2:endEvent id="_3" name="NormalEnd">
+ <bpmn2:incoming>_2-_3</bpmn2:incoming>
+ </bpmn2:endEvent>
+
+ <!-- error boundary event: catches errors with errorCode
"subprocess-fault" -->
+ <bpmn2:boundaryEvent id="_4" name="ErrorBoundary" attachedToRef="_2"
cancelActivity="true">
+ <bpmn2:outgoing>_4-_5</bpmn2:outgoing>
+ <bpmn2:errorEventDefinition id="_4_err"
errorRef="_subprocessFaultError"/>
+ </bpmn2:boundaryEvent>
+
+ <!-- script to record that the boundary event was triggered -->
+ <bpmn2:scriptTask id="_5" name="RecordError"
scriptFormat="http://www.java.com/java">
+ <bpmn2:incoming>_4-_5</bpmn2:incoming>
+ <bpmn2:outgoing>_5-_6</bpmn2:outgoing>
+ <bpmn2:script>kcontext.setVariable("result",
"error-caught");</bpmn2:script>
+ </bpmn2:scriptTask>
+
+ <!-- error-handled end event -->
+ <bpmn2:endEvent id="_6" name="ErrorHandledEnd">
+ <bpmn2:incoming>_5-_6</bpmn2:incoming>
+ </bpmn2:endEvent>
+
+ <bpmn2:sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <bpmn2:sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ <bpmn2:sequenceFlow id="_4-_5" sourceRef="_4" targetRef="_5"/>
+ <bpmn2:sequenceFlow id="_5-_6" sourceRef="_5" targetRef="_6"/>
+ </bpmn2:process>
+
+</bpmn2:definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2
new file mode 100644
index 00000000000..330fdcca5b1
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2
@@ -0,0 +1,49 @@
+<?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.
+ -->
+
+<!-- Child process A — sets outcome="A" so the parent can verify which was
called -->
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ xmlns:tns="http://www.jboss.org/drools">
+
+ <process processType="Private" isExecutable="true"
+ id="DynamicSubProcessA"
+ name="Dynamic SubProcess A"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <startEvent id="_1" name="Start"/>
+ <scriptTask id="_2" name="MarkA">
+ <script>System.out.println("DynamicSubProcessA running");</script>
+ </scriptTask>
+ <endEvent id="_3" name="End">
+ <terminateEventDefinition/>
+ </endEvent>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2
new file mode 100644
index 00000000000..cbec556f00d
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2
@@ -0,0 +1,49 @@
+<?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.
+ -->
+
+<!-- Child process B — sets outcome="B" so the parent can verify which was
called -->
+<definitions id="Definition"
+ targetNamespace="http://www.example.org/MinimalExample"
+ typeLanguage="http://www.java.com/javaTypes"
+ expressionLanguage="http://www.mvel.org/2.0"
+ xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
BPMN20.xsd"
+ xmlns:tns="http://www.jboss.org/drools">
+
+ <process processType="Private" isExecutable="true"
+ id="DynamicSubProcessB"
+ name="Dynamic SubProcess B"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess">
+
+ <startEvent id="_1" name="Start"/>
+ <scriptTask id="_2" name="MarkB">
+ <script>System.out.println("DynamicSubProcessB running");</script>
+ </scriptTask>
+ <endEvent id="_3" name="End">
+ <terminateEventDefinition/>
+ </endEvent>
+
+ <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+ </process>
+
+</definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2
new file mode 100644
index 00000000000..4d31770d708
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2
@@ -0,0 +1,63 @@
+<?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.
+ -->
+
+<!--
+ Faulty subprocess that immediately ends with an error end event
+ (errorCode="subprocess-fault"). Used by the boundary error tests
+ to verify that a parent callActivity's Error Boundary Event fires
+ when the called subprocess propagates a fault.
+-->
+<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:tns="http://www.jboss.org/drools"
+ xmlns="http://www.jboss.org/drools"
+ xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd
http://www.jboss.org/drools drools.xsd"
+ id="Definition"
+ expressionLanguage="http://www.mvel.org/2.0"
+ targetNamespace="http://www.jboss.org/drools"
+ typeLanguage="http://www.java.com/javaTypes">
+
+ <bpmn2:error id="_fault" errorCode="subprocess-fault" name="Subprocess
Fault"/>
+
+ <bpmn2:process id="DynamicSubProcessFaulty"
+ tns:version="1"
+ tns:packageName="org.jbpm.bpmn2.subprocess"
+ name="Dynamic SubProcess Faulty"
+ isExecutable="true"
+ processType="Private">
+
+ <bpmn2:startEvent id="_1" name="Start">
+ <bpmn2:outgoing>_1-_2</bpmn2:outgoing>
+ </bpmn2:startEvent>
+
+ <!-- Error end event immediately propagates "subprocess-fault" to parent
-->
+ <bpmn2:endEvent id="_2" name="FaultEnd">
+ <bpmn2:incoming>_1-_2</bpmn2:incoming>
+ <bpmn2:errorEventDefinition id="_2_err" errorRef="_fault"/>
+ </bpmn2:endEvent>
+
+ <bpmn2:sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+ </bpmn2:process>
+
+</bpmn2:definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/DynamicSubProcessTest.java
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/DynamicSubProcessTest.java
new file mode 100644
index 00000000000..fe675074bf0
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/DynamicSubProcessTest.java
@@ -0,0 +1,468 @@
+/*
+ * 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.jbpm.bpmn2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jbpm.process.instance.impl.ProcessInstanceImpl;
+import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
+import org.junit.jupiter.api.Test;
+import org.kie.api.event.process.ProcessCompletedEvent;
+import org.kie.api.event.process.ProcessStartedEvent;
+import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
+import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcessInstance;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests dynamic subprocess resolution where a BPMN {@code callActivity}
+ * {@code calledElement} may contain an MVEL expression evaluated at runtime.
+ *
+ * Covers:
+ * <ul>
+ * <li>Static and dynamic subprocess IDs</li>
+ * <li>Simple and compound MVEL expressions</li>
+ * <li>I/O data mappings</li>
+ * <li>Asynchronous execution ({@code waitForCompletion=false})</li>
+ * <li>Root process metadata propagation</li>
+ * <li>Invalid process IDs</li>
+ * <li>Repeated invocations with different variables</li>
+ * </ul>
+ */
+public class DynamicSubProcessTest extends JbpmBpmn2TestCase {
+
+ @Test
+ public void testCallActivityWithStaticProcessId() throws Exception {
+ kruntime = createKogitoProcessRuntime(
+ "org/jbpm/bpmn2/subprocess/BPMN2-CallActivity.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("x", "oldValue");
+
+ KogitoProcessInstance processInstance =
kruntime.startProcess("CallActivity", params);
+
+ assertProcessInstanceCompleted(processInstance);
+ assertThat(((KogitoWorkflowProcessInstance)
processInstance).getVariable("y"))
+ .isEqualTo("new value");
+ }
+
+ @Test
+ public void testCallActivityWithSimpleVariableExpression() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "CallActivitySubProcess");
+ params.put("x", "dynamicInput");
+
+ KogitoProcessInstance processInstance = kruntime.startProcess(
+ "DynamicCallActivityByVariable", params);
+
+ assertProcessInstanceCompleted(processInstance);
+ assertThat(((KogitoWorkflowProcessInstance)
processInstance).getVariable("y"))
+ .isEqualTo("new value");
+ }
+
+ @Test
+ public void testCallActivityWithMvelCompoundExpression() throws Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("prefix", "CallActivity");
+ params.put("x", "expressionInput");
+
+ KogitoProcessInstance processInstance = kruntime.startProcess(
+ "DynamicCallActivityByExpression", params);
+
+ assertProcessInstanceCompleted(processInstance);
+ assertThat(((KogitoWorkflowProcessInstance)
processInstance).getVariable("y"))
+ .isEqualTo("new value");
+ }
+
+ @Test
+ public void testCallActivityDynamicWithIoMappings() throws Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "CallActivitySubProcess");
+ params.put("x", "myInputValue");
+
+ KogitoProcessInstance processInstance = kruntime.startProcess(
+ "DynamicCallActivityByVariable", params);
+
+ assertProcessInstanceCompleted(processInstance);
+ // Subprocess sets subY = "new value"; it must flow back to parent
variable y
+ String y = (String) ((KogitoWorkflowProcessInstance)
processInstance).getVariable("y");
+ assertThat(y).isEqualTo("new value");
+ }
+
+ @Test
+ public void testCallActivityDynamicNoWait() throws Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ final List<String> startedSubprocesses = new ArrayList<>();
+ kruntime.getProcessEventManager().addEventListener(
+ new org.kie.api.event.process.DefaultProcessEventListener() {
+ @Override
+ public void beforeProcessStarted(ProcessStartedEvent
event) {
+ if
("CallActivitySubProcess".equals(event.getProcessInstance().getProcessId())) {
+ startedSubprocesses.add(((KogitoProcessInstance)
event.getProcessInstance()).getStringId());
+ }
+ }
+ });
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "CallActivitySubProcess");
+
+ KogitoProcessInstance processInstance = kruntime.startProcess(
+ "DynamicCallActivityNoWait", params);
+
+ // Parent must have completed already
+ assertProcessInstanceCompleted(processInstance);
+ // Child was spawned
+ assertThat(startedSubprocesses).hasSize(1);
+ }
+
+ @Test
+ public void testRootProcessMetadataPropagation() throws Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ final List<ProcessInstanceImpl> childInstances = new ArrayList<>();
+ kruntime.getProcessEventManager().addEventListener(
+ new org.kie.api.event.process.DefaultProcessEventListener() {
+ @Override
+ public void beforeProcessStarted(ProcessStartedEvent
event) {
+ if
("CallActivitySubProcess".equals(event.getProcessInstance().getProcessId())) {
+ childInstances.add((ProcessInstanceImpl)
event.getProcessInstance());
+ }
+ }
+ });
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "CallActivitySubProcess");
+ params.put("x", "rootTest");
+
+ KogitoProcessInstance parentInstance = kruntime.startProcess(
+ "DynamicCallActivityByVariable", params);
+
+ assertProcessInstanceCompleted(parentInstance);
+ assertThat(childInstances).hasSize(1);
+
+ ProcessInstanceImpl child = childInstances.get(0);
+ // When the parent is the root, rootProcessId must equal the parent's
processId
+
assertThat(child.getRootProcessId()).isEqualTo("DynamicCallActivityByVariable");
+ // rootProcessInstanceId must equal the parent's instance id
+
assertThat(child.getRootProcessInstanceId()).isEqualTo(parentInstance.getStringId());
+ // rootProcessVersion must equal the parent's declared version "1"
+ assertThat(child.getRootProcessVersion()).isEqualTo("1");
+ }
+
+ @Test
+ public void testCallActivityDynamicUnknownProcessEndsInError() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "NonExistentProcess");
+
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityUnknownProcess",
params);
+
+ // The runtime catches the IllegalArgumentException and marks the
instance as ERROR
+
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+ assertThat(((WorkflowProcessInstanceImpl)
processInstance).getErrorMessage())
+ .contains("NonExistentProcess");
+ }
+
+ @Test
+ public void testCallActivityDynamicNullSubprocessIdEndsInError() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", null);
+
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityUnknownProcess",
params);
+
+
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+ assertThat(((WorkflowProcessInstanceImpl)
processInstance).getErrorMessage())
+ .contains("Could not find process");
+ }
+
+ @Test
+ public void testCallActivityDynamicEmptySubprocessIdEndsInError() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "");
+
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityUnknownProcess",
params);
+
+
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+ assertThat(((WorkflowProcessInstanceImpl)
processInstance).getErrorMessage())
+ .contains("Could not find process");
+ }
+
+ @Test
+ public void testCallActivityDynamicBlankSubprocessIdEndsInError() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", " ");
+
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityUnknownProcess",
params);
+
+
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+ assertThat(((WorkflowProcessInstanceImpl)
processInstance).getErrorMessage())
+ .contains("Could not find process");
+ }
+
+ @Test
+ public void testCallActivityDynamicMissingVariableEndsInError() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2");
+
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityMissingVariable");
+
+
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+ assertThat(((WorkflowProcessInstanceImpl)
processInstance).getErrorMessage())
+ .contains("Could not find process");
+ }
+
+ @Test
+ public void testDynamicSubprocessFaultPropagatesAsAbort() throws Exception
{
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "DynamicSubProcessFaulty");
+
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityUnknownProcess",
params);
+
+ // When the subprocess faults and the parent has no matching exception
handler,
+ // the parent completes normally (fault is silently absorbed, not
propagated).
+ // This is the default kruntime behaviour when abortParent=false (the
default).
+ assertThat(processInstance.getState())
+ .as("Without a boundary event, parent should complete normally
when child faults")
+ .isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+ }
+
+ @Test
+ public void testCallActivityWithScriptResolvedSubprocessId() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ // No subprocessId in params — the script inside the process sets it
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityScriptResolved");
+
+ assertProcessInstanceCompleted(processInstance);
+ // CallActivitySubProcess sets subY="new value"; mapped back to y
+ assertThat(((KogitoWorkflowProcessInstance)
processInstance).getVariable("y"))
+ .isEqualTo("new value");
+ }
+
+ @Test
+ public void testTwoSequentialCallsDispatchToDifferentSubprocesses() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2",
+ "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2",
+ "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2");
+
+ final List<String> childIds = new ArrayList<>();
+ kruntime.getProcessEventManager().addEventListener(
+ new org.kie.api.event.process.DefaultProcessEventListener() {
+ @Override
+ public void beforeProcessStarted(ProcessStartedEvent
event) {
+ String pid = event.getProcessInstance().getProcessId();
+ if (!"DynamicCallActivityTwoSequential".equals(pid)) {
+ childIds.add(pid);
+ }
+ }
+ });
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "DynamicSubProcessA"); // first call uses
this
+ KogitoProcessInstance processInstance =
+ kruntime.startProcess("DynamicCallActivityTwoSequential",
params);
+
+ assertProcessInstanceCompleted(processInstance);
+ // Two child processes must have been spawned in order
+ assertThat(childIds)
+ .hasSize(2)
+ .containsExactly("DynamicSubProcessA", "DynamicSubProcessB");
+ }
+
+ @Test
+ public void testChildProcessReachesStateCompleted() throws Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ final List<KogitoProcessInstance> completedChildren = new
ArrayList<>();
+ kruntime.getProcessEventManager().addEventListener(
+ new org.kie.api.event.process.DefaultProcessEventListener() {
+ @Override
+ public void afterProcessCompleted(ProcessCompletedEvent
event) {
+ if ("CallActivitySubProcess".equals(
+ event.getProcessInstance().getProcessId())) {
+ completedChildren.add((KogitoProcessInstance)
event.getProcessInstance());
+ }
+ }
+ });
+
+ Map<String, Object> params = new HashMap<>();
+ params.put("subprocessId", "CallActivitySubProcess");
+ params.put("x", "completionCheck");
+
+ KogitoProcessInstance parent =
kruntime.startProcess("DynamicCallActivityByVariable", params);
+
+ assertProcessInstanceCompleted(parent);
+ assertThat(completedChildren).hasSize(1);
+ assertThat(completedChildren.get(0).getState())
+ .isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+ }
+
+ @Test
+ public void testConcurrentParentInstancesResolveIndependently() throws
Exception {
+ // Use the no-wait parent (no I/O mappings) to avoid variable-schema
+ // mismatches with the simple child processes A and B.
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2",
+ "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2",
+ "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2");
+
+ // Pair each parent instance ID with the child it spawned
+ final Map<String, String> parentToChild = new HashMap<>();
+ kruntime.getProcessEventManager().addEventListener(
+ new org.kie.api.event.process.DefaultProcessEventListener() {
+ @Override
+ public void beforeProcessStarted(ProcessStartedEvent
event) {
+ KogitoProcessInstance child = (KogitoProcessInstance)
event.getProcessInstance();
+ String childPid = child.getProcessId();
+ if (!childPid.equals("DynamicCallActivityNoWait")) {
+ ProcessInstanceImpl pi = (ProcessInstanceImpl)
child;
+ String parentInstanceId = (String)
pi.getMetaData().get("ParentProcessInstanceId");
+ parentToChild.put(parentInstanceId, childPid);
+ }
+ }
+ });
+
+ Map<String, Object> paramsA = new HashMap<>();
+ paramsA.put("subprocessId", "DynamicSubProcessA");
+ KogitoProcessInstance piA =
kruntime.startProcess("DynamicCallActivityNoWait", paramsA);
+
+ Map<String, Object> paramsB = new HashMap<>();
+ paramsB.put("subprocessId", "DynamicSubProcessB");
+ KogitoProcessInstance piB =
kruntime.startProcess("DynamicCallActivityNoWait", paramsB);
+
+ // no-wait parent completes immediately
+ assertProcessInstanceCompleted(piA);
+ assertProcessInstanceCompleted(piB);
+
+ // Each parent must have dispatched to exactly its own child
+
assertThat(parentToChild.get(piA.getStringId())).isEqualTo("DynamicSubProcessA");
+
assertThat(parentToChild.get(piB.getStringId())).isEqualTo("DynamicSubProcessB");
+ }
+
+ @Test
+ public void testCallActivityDynamicDispatchedIndependentlyPerInstance()
throws Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ final List<String> childProcessIds = new ArrayList<>();
+ kruntime.getProcessEventManager().addEventListener(
+ new org.kie.api.event.process.DefaultProcessEventListener() {
+ @Override
+ public void beforeProcessStarted(ProcessStartedEvent
event) {
+ if
(!"DynamicCallActivityByVariable".equals(event.getProcessInstance().getProcessId()))
{
+
childProcessIds.add(event.getProcessInstance().getProcessId());
+ }
+ }
+ });
+
+ // First invocation
+ Map<String, Object> params1 = new HashMap<>();
+ params1.put("subprocessId", "CallActivitySubProcess");
+ params1.put("x", "first");
+ KogitoProcessInstance pi1 =
kruntime.startProcess("DynamicCallActivityByVariable", params1);
+ assertProcessInstanceCompleted(pi1);
+
+ // Second invocation (same subprocess, new instance – verifies
stateless resolution)
+ Map<String, Object> params2 = new HashMap<>();
+ params2.put("subprocessId", "CallActivitySubProcess");
+ params2.put("x", "second");
+ KogitoProcessInstance pi2 =
kruntime.startProcess("DynamicCallActivityByVariable", params2);
+ assertProcessInstanceCompleted(pi2);
+
+ // Both dispatches hit CallActivitySubProcess
+ assertThat(childProcessIds)
+ .hasSize(2)
+ .containsOnly("CallActivitySubProcess");
+ }
+
+ /**
+ * Verifies that a {@code calledElement} built from <em>multiple</em>
{@code #{...}}
+ * tokens (e.g. {@code "#{prefix}#{suffix}"}) is fully resolved before the
subprocess
+ * is looked up. This mirrors the behaviour supported in legacy 7.67.x
+ * {@code SubProcessNodeInstance}.
+ */
+ @Test
+ public void testCallActivityWithMultipleVariableExpressions() throws
Exception {
+ kruntime = createKogitoProcessRuntime(
+
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2",
+
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+ Map<String, Object> params = new HashMap<>();
+ // "#{prefix}#{suffix}" resolves to "CallActivity" + "SubProcess" =
"CallActivitySubProcess"
+ params.put("prefix", "CallActivity");
+ params.put("suffix", "SubProcess");
+ params.put("x", "multiExprInput");
+
+ KogitoProcessInstance processInstance = kruntime.startProcess(
+ "DynamicCallActivityMultiExpression", params);
+
+ assertProcessInstanceCompleted(processInstance);
+ assertThat(((KogitoWorkflowProcessInstance)
processInstance).getVariable("y"))
+ .isEqualTo("new value");
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]