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]

Reply via email to