This is an automated email from the ASF dual-hosted git repository.
martinweiler pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git
The following commit(s) were added to refs/heads/main by this push:
new 02bbbbecc04 [kie-issues#7256] Align interrupting event sub process
with BPMN 2.0 specifiction, section 13.4.4 (#7257)
02bbbbecc04 is described below
commit 02bbbbecc047212d03eeb62975dbb61cc2d48d34
Author: Martin Weiler <[email protected]>
AuthorDate: Thu Nov 5 11:28:29 2026 -0600
[kie-issues#7256] Align interrupting event sub process with BPMN 2.0
specifiction, section 13.4.4 (#7257)
* [kie-issues#7256] Align interrupting event sub process with BPMN 2.0
specifiction, section 13.4.4
* Prevent auto-completion when the interrupting event sub process finishes
* Fix failing ErrorIT test case
---
.../instance/impl/WorkflowProcessInstanceImpl.java | 4 +
.../instance/node/EventSubProcessNodeInstance.java | 40 ++++++++++
...-EventSubprocessSignalInterruptingWaiting.bpmn2 | 92 ++++++++++++++++++++++
.../java/org/jbpm/bpmn2/IntermediateEventTest.java | 30 +++++++
4 files changed, 166 insertions(+)
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
index 9a30f446515..c4faca5c9a2 100755
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
+++
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
@@ -1110,6 +1110,10 @@ public abstract class WorkflowProcessInstanceImpl
extends ProcessInstanceImpl im
if (nodeInstance instanceof FaultNodeInstance || nodeInstance
instanceof EndNodeInstance ||
((org.jbpm.workflow.core.WorkflowProcess)
getWorkflowProcess()).isDynamic()
|| nodeInstance instanceof CompositeNodeInstance) {
+ if (nodeInstance instanceof EventSubProcessNodeInstance
eventSubProcess && eventSubProcess.isInterrupting()) {
+ // the interrupting event sub process aborts this process
instance itself, it must not be completed here
+ return;
+ }
if (((org.jbpm.workflow.core.WorkflowProcess)
getProcess()).isAutoComplete() && canComplete()) {
setState(KogitoProcessInstance.STATE_COMPLETED);
}
diff --git
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventSubProcessNodeInstance.java
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventSubProcessNodeInstance.java
index aad5c88209e..e3707b4dece 100755
---
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventSubProcessNodeInstance.java
+++
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventSubProcessNodeInstance.java
@@ -18,6 +18,7 @@
*/
package org.jbpm.workflow.instance.node;
+import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
@@ -68,6 +69,13 @@ public class EventSubProcessNodeInstance extends
CompositeContextNodeInstance {
if (this.getNodeInstances().isEmpty()) {
StartNode startNode = getCompositeNode().findStartNode();
if (resolveVariables(((EventSubProcessNode)
getEventBasedNode()).getEvents()).contains(type) ||
type.equals(TIMER_TRIGGERED_EVENT)) {
+ // BPMN 2.0 13.4.4: once an interrupting handler is
started, no new handlers can be started
+ if (isInterruptedByRunningHandler()) {
+ return;
+ }
+ if (isInterrupting(startNode)) {
+ interruptContainer();
+ }
NodeInstance nodeInstance = getNodeInstance(startNode);
((StartNodeInstance) nodeInstance).signalEvent(type,
event);
}
@@ -76,6 +84,38 @@ public class EventSubProcessNodeInstance extends
CompositeContextNodeInstance {
super.signalEvent(type, event, varResolver);
}
+ public boolean isInterrupting() {
+ return isInterrupting(getCompositeNode().findStartNode());
+ }
+
+ private boolean isInterrupting(StartNode startNode) {
+ return getCompositeNode().isKeepActive() && startNode.isInterrupting();
+ }
+
+ private static boolean isRunningInterruptingHandler(NodeInstance
nodeInstance) {
+ return nodeInstance instanceof EventSubProcessNodeInstance handler
+ && !handler.getNodeInstances().isEmpty()
+ &&
handler.isInterrupting(handler.getCompositeNode().findStartNode());
+ }
+
+ private boolean isInterruptedByRunningHandler() {
+ return getNodeInstanceContainer().getNodeInstances().stream()
+ .anyMatch(ni -> ni != this &&
isRunningInterruptingHandler(ni));
+ }
+
+ /**
+ * BPMN 2.0 13.4.4 / Activity lifecycle: when an interrupting event sub
process is initiated the parent is interrupted,
+ * i.e. all its other active node instances are terminated (the handler
itself keeps running), and no new event sub
+ * process can be started. The parent itself reaches its final state once
the handler completes.
+ */
+ private void interruptContainer() {
+ for (NodeInstance nodeInstance : new
ArrayList<>(getNodeInstanceContainer().getNodeInstances())) {
+ if (nodeInstance != this &&
!isRunningInterruptingHandler(nodeInstance) &&
getNodeInstanceContainer().getNodeInstances().contains(nodeInstance)) {
+ ((org.jbpm.workflow.instance.NodeInstance)
nodeInstance).cancel(org.jbpm.workflow.instance.NodeInstance.CancelType.ABORTED);
+ }
+ }
+ }
+
@Override
public void nodeInstanceCompleted(org.jbpm.workflow.instance.NodeInstance
nodeInstance, String outType) {
if (nodeInstance instanceof EndNodeInstance) {
diff --git
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/intermediate/BPMN2-EventSubprocessSignalInterruptingWaiting.bpmn2
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/intermediate/BPMN2-EventSubprocessSignalInterruptingWaiting.bpmn2
new file mode 100644
index 00000000000..7a1e8caf798
--- /dev/null
+++
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/intermediate/BPMN2-EventSubprocessSignalInterruptingWaiting.bpmn2
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:tns="http://www.jboss.org/drools"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
id="Definitions_1" expressionLanguage="http://www.mvel.org/2.0"
targetNamespace="http://w [...]
+ <bpmn2:process id="EventSubprocessSignalInterruptingWaiting" tns:version="1"
tns:packageName="org.jbpm.bpmn2.intermediate" name="Default Process">
+ <bpmn2:startEvent id="StartEvent_1" name="start">
+ <bpmn2:outgoing>SequenceFlow_2</bpmn2:outgoing>
+ </bpmn2:startEvent>
+ <bpmn2:sequenceFlow id="SequenceFlow_1" tns:priority="1"
sourceRef="UserTask_1" targetRef="EndEvent_1"/>
+ <bpmn2:endEvent id="EndEvent_1" name="end">
+ <bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
+ </bpmn2:endEvent>
+ <bpmn2:userTask id="UserTask_1" name="User Task 1">
+ <bpmn2:incoming>SequenceFlow_2</bpmn2:incoming>
+ <bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
+ </bpmn2:userTask>
+ <bpmn2:sequenceFlow id="SequenceFlow_2" tns:priority="1" name=""
sourceRef="StartEvent_1" targetRef="UserTask_1"/>
+ <bpmn2:subProcess id="SubProcess_1" name="Sub Process 1"
triggeredByEvent="true" >
+ <bpmn2:startEvent id="StartEvent_2" name="start-sub">
+ <bpmn2:outgoing>SequenceFlow_3</bpmn2:outgoing>
+ <bpmn2:signalEventDefinition id="SignalEventDefinition_1"
signalRef="MySignal"/>
+ </bpmn2:startEvent>
+ <bpmn2:userTask id="UserTask_2" name="Handler User Task">
+ <bpmn2:incoming>SequenceFlow_3</bpmn2:incoming>
+ <bpmn2:outgoing>SequenceFlow_4</bpmn2:outgoing>
+ </bpmn2:userTask>
+ <bpmn2:sequenceFlow id="SequenceFlow_3" tns:priority="1"
sourceRef="StartEvent_2" targetRef="UserTask_2"/>
+ <bpmn2:endEvent id="EndEvent_2" name="end-sub">
+ <bpmn2:incoming>SequenceFlow_4</bpmn2:incoming>
+ </bpmn2:endEvent>
+ <bpmn2:sequenceFlow id="SequenceFlow_4" tns:priority="1"
sourceRef="UserTask_2" targetRef="EndEvent_2"/>
+ </bpmn2:subProcess>
+ </bpmn2:process>
+ <bpmndi:BPMNDiagram id="BPMNDiagram_1" name="Default Process Diagram">
+ <bpmndi:BPMNPlane id="BPMNPlane_1"
bpmnElement="EventSubprocessSignalInterruptingWaiting">
+ <bpmndi:BPMNShape id="BPMNShape_1" bpmnElement="StartEvent_1">
+ <dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="BPMNShape_2" bpmnElement="EndEvent_1">
+ <dc:Bounds height="36.0" width="36.0" x="500.0" y="100.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="BPMNShape_UserTask_1" bpmnElement="UserTask_1">
+ <dc:Bounds height="50.0" width="110.0" x="270.0" y="93.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1"
bpmnElement="SequenceFlow_1" sourceElement="BPMNShape_UserTask_1"
targetElement="BPMNShape_2">
+ <di:waypoint xsi:type="dc:Point" x="380.0" y="118.0"/>
+ <di:waypoint xsi:type="dc:Point" x="500.0" y="118.0"/>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_2"
bpmnElement="SequenceFlow_2" sourceElement="BPMNShape_1"
targetElement="BPMNShape_UserTask_1">
+ <di:waypoint xsi:type="dc:Point" x="136.0" y="118.0"/>
+ <di:waypoint xsi:type="dc:Point" x="270.0" y="118.0"/>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNShape id="BPMNShape_SubProcess_1" bpmnElement="SubProcess_1"
isExpanded="true">
+ <dc:Bounds height="131.0" width="381.0" x="180.0" y="240.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="BPMNShape_StartEvent_1" bpmnElement="StartEvent_2">
+ <dc:Bounds height="36.0" width="36.0" x="232.0" y="287.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="BPMNShape_UserTask_2" bpmnElement="UserTask_2">
+ <dc:Bounds height="50.0" width="110.0" x="318.0" y="280.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_3"
bpmnElement="SequenceFlow_3" sourceElement="BPMNShape_StartEvent_1"
targetElement="BPMNShape_UserTask_2">
+ <di:waypoint xsi:type="dc:Point" x="268.0" y="305.0"/>
+ <di:waypoint xsi:type="dc:Point" x="318.0" y="305.0"/>
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNShape id="BPMNShape_EndEvent_1" bpmnElement="EndEvent_2">
+ <dc:Bounds height="36.0" width="36.0" x="478.0" y="287.0"/>
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_4"
bpmnElement="SequenceFlow_4" sourceElement="BPMNShape_UserTask_2"
targetElement="BPMNShape_EndEvent_1">
+ <di:waypoint xsi:type="dc:Point" x="428.0" y="305.0"/>
+ <di:waypoint xsi:type="dc:Point" x="478.0" y="305.0"/>
+ </bpmndi:BPMNEdge>
+ </bpmndi:BPMNPlane>
+ </bpmndi:BPMNDiagram>
+</bpmn2:definitions>
diff --git
a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java
index 6a57dc9fb88..24b47fc5e07 100755
---
a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java
+++
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java
@@ -888,6 +888,36 @@ public class IntermediateEventTest extends
JbpmBpmn2TestCase {
assertThatIterable(tracker.tracked()).extracting(a ->
a.getNodeInstance().getNodeName()).contains(nodes);
}
+ @Test
+ public void testEventSubprocessSignalInterruptingWaitingHandler() {
+ Application app = ProcessTestHelper.newApplication();
+ TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
+ ProcessTestHelper.registerHandler(app, "Human Task", workItemHandler);
+
+
org.kie.kogito.process.Process<EventSubprocessSignalInterruptingWaitingModel>
processDefinition =
EventSubprocessSignalInterruptingWaitingProcess.newProcess(app);
+
org.kie.kogito.process.ProcessInstance<EventSubprocessSignalInterruptingWaitingModel>
instance = processDefinition.createInstance(processDefinition.createModel());
+ instance.start();
+
assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE);
+
assertThat(instance.workItems()).extracting(WorkItem::getNodeInstanceId).hasSize(1);
+ KogitoWorkItem mainTask = workItemHandler.getWorkItem();
+
+ instance.send(SignalFactory.of("MySignal"));
+
+ // the interrupting handler is running: the parent flow was
interrupted, only the handler's task is left
+
assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE);
+ KogitoWorkItem handlerTask = workItemHandler.getWorkItem();
+
assertThat(handlerTask.getStringId()).isNotEqualTo(mainTask.getStringId());
+
assertThat(instance.workItems()).extracting(WorkItem::getId).containsExactly(handlerTask.getStringId());
+
+ // a second trigger must not start another handler while the parent is
being terminated
+ instance.send(SignalFactory.of("MySignal"));
+
assertThat(instance.workItems()).extracting(WorkItem::getId).containsExactly(handlerTask.getStringId());
+
+ // the parent reaches its final state once the handler is done
+ instance.completeWorkItem(handlerTask.getStringId(), null);
+ assertThat(instance.status()).isEqualTo(ProcessInstance.STATE_ABORTED);
+ }
+
@Test
public void testEventSubprocessMessage() throws Exception {
Application app = ProcessTestHelper.newApplication();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]