Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.flowable.engine.delegate.BpmnError;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.event.impl.FlowableEventBuilder;
import org.flowable.engine.history.DeleteReason;
import org.flowable.common.engine.impl.callback.CallbackData;
import org.flowable.common.engine.impl.callback.RuntimeInstanceStateChangeCallback;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
Expand Down Expand Up @@ -298,14 +299,16 @@ protected static void executeEventHandler(Event event, ExecutionEntity parentExe
LOGGER.debug(
"Ending and deleting child executions for parent execution '{}'. Reason: Parent Execution is processIntanceType. Current Execution '{}'",
parentExecution, currentExecution);
executionEntityManager.deleteChildExecutions(parentExecution, null, true);
executionEntityManager.deleteChildExecutions(parentExecution,
DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + event.getId() + ")", true);
} else if (!currentExecution.getParentId().equals(parentExecution.getId())) {
LOGGER.debug("Planing destroyScopeOperation for execution {}. Reason: {}. Parent execution: {}", currentExecution,
"Current execution parentId odes not match parentExecution id", parentExecution);
CommandContextUtil.getAgenda().planDestroyScopeOperation(currentExecution);
} else {
LOGGER.debug("Deleting execution and related data for execution {}.", currentExecution);
executionEntityManager.deleteExecutionAndRelatedData(currentExecution, null, false);
executionEntityManager.deleteExecutionAndRelatedData(currentExecution,
DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + event.getId() + ")", false);
}

ExecutionEntity eventSubProcessExecution = executionEntityManager.createChildExecution(parentExecution);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* Licensed 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.flowable.engine.test.api.deletereason;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.flowable.engine.history.DeleteReason;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.impl.test.PluggableFlowableTestCase;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.test.Deployment;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;

/**
* Companion to {@link DeleteReasonTest#testInterruptingBoundaryEvent()}, for the
* interrupting error event sub-process.
*
* An activity terminated by an interrupting start event is recorded with
* DeleteReason.EVENT_SUBPROCESS_INTERRUPTING, so history can tell it apart from
* one that completed normally.
*/
public class ErrorEventSubProcessDeleteReasonTest extends PluggableFlowableTestCase {

/**
* A forked process: one branch parks on a user task inside a call activity,
* the other throws a BpmnError from a start execution listener. The
* interrupting error event sub-process catches it and terminates the whole
* scope, including the parked branch.
*/
@Test
@Deployment(resources = "org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.bpmn20.xml")
public void testInterruptingErrorEventSubProcess() {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("errorEventSubProcessDeleteReason");

waitForHistoryJobExecutorToProcessAllJobs(7000, 100);

// The parked branch's user task was destroyed by the interruption.
assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()).isZero();
assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isZero();

HistoricActivityInstance parkedBranch = historicActivity(processInstance.getId(), "waitingCall");

// An end time and a called process instance are also what a call
// activity that ran to completion records, so the delete reason is the
// only thing separating the two.
assertThat(parkedBranch.getEndTime()).isNotNull();
assertThat(parkedBranch.getCalledProcessInstanceId()).isNotNull();

assertThat(parkedBranch.getDeleteReason())
.as("delete reason on an activity terminated by the interrupting error event sub-process")
.isEqualTo(DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(errorHandlerStart)");
}

/**
* The same BpmnError caught by an interrupting boundary event instead, which
* records DeleteReason.BOUNDARY_EVENT_INTERRUPTING. The two paths differ only
* in which vehicle catches the error.
*/
@Test
@Deployment(resources = "org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.boundary.bpmn20.xml")
public void testInterruptingBoundaryEvent() {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("boundaryDeleteReason");

Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
assertThat(task).isNotNull();
taskService.complete(task.getId());

waitForHistoryJobExecutorToProcessAllJobs(7000, 100);

HistoricActivityInstance subProcess = historicActivity(processInstance.getId(), "theSubProcess");
assertThat(subProcess.getDeleteReason()).contains(DeleteReason.BOUNDARY_EVENT_INTERRUPTING);
}

private HistoricActivityInstance historicActivity(String processInstanceId, String activityId) {
List<HistoricActivityInstance> instances = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.activityId(activityId)
.list();
assertThat(instances).hasSize(1);
return instances.get(0);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* Licensed 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.flowable.engine.test.api.deletereason;

import org.flowable.engine.delegate.BpmnError;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;

/** Raises a BpmnError from a start execution listener. */
public class ThrowBpmnErrorListener implements ExecutionListener {

@Override
public void notify(DelegateExecution execution) {
throw new BpmnError("someError", "raised from a start execution listener");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="deleteReason">

<error id="someError" errorCode="someError"/>

<!--
The same BpmnError as the event sub-process case, caught by a boundary event
instead. BoundaryEventActivityBehavior records
DeleteReason.BOUNDARY_EVENT_INTERRUPTING on the activity it interrupts, so
the only difference between this file and the other one is which vehicle
catches the error.
-->
<process id="boundaryDeleteReason" isExecutable="true">

<startEvent id="start"/>
<sequenceFlow id="toSubProcess" sourceRef="start" targetRef="theSubProcess"/>

<subProcess id="theSubProcess" name="The Sub Process">
<startEvent id="innerStart"/>
<sequenceFlow id="innerToTask" sourceRef="innerStart" targetRef="innerTask"/>
<userTask id="innerTask" name="Inner Task" flowable:assignee="kermit"/>
<sequenceFlow id="innerTaskToError" sourceRef="innerTask" targetRef="innerErrorEnd"/>
<endEvent id="innerErrorEnd">
<errorEventDefinition errorRef="someError"/>
</endEvent>
</subProcess>

<sequenceFlow id="subProcessToEnd" sourceRef="theSubProcess" targetRef="endNormal"/>
<endEvent id="endNormal"/>

<boundaryEvent id="errorBoundary" attachedToRef="theSubProcess" cancelActivity="true">
<errorEventDefinition/>
</boundaryEvent>
<sequenceFlow id="boundaryToEnd" sourceRef="errorBoundary" targetRef="endCaught"/>
<endEvent id="endCaught"/>

</process>
</definitions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="deleteReason">

<process id="errorEventSubProcessDeleteReason" isExecutable="true">

<startEvent id="start"/>
<sequenceFlow id="toFork" sourceRef="start" targetRef="fork"/>
<parallelGateway id="fork"/>

<!-- Branch A: a call activity whose child parks on a user task. -->
<sequenceFlow id="toWaiting" sourceRef="fork" targetRef="waitingCall"/>
<callActivity id="waitingCall" name="Waiting Call" calledElement="waitingChild"/>
<sequenceFlow id="waitingToEnd" sourceRef="waitingCall" targetRef="endA"/>
<endEvent id="endA"/>

<!-- Branch B: a start execution listener raises a BpmnError. -->
<sequenceFlow id="toThrowing" sourceRef="fork" targetRef="throwingTask"/>
<serviceTask id="throwingTask" name="Throwing Task" flowable:expression="${true}">
<extensionElements>
<flowable:executionListener event="start"
class="org.flowable.engine.test.api.deletereason.ThrowBpmnErrorListener"/>
</extensionElements>
</serviceTask>
<sequenceFlow id="throwingToEnd" sourceRef="throwingTask" targetRef="endB"/>
<endEvent id="endB"/>

<!-- Interrupting error event sub-process: catches, and terminates the scope. -->
<subProcess id="errorHandler" triggeredByEvent="true">
<startEvent id="errorHandlerStart" isInterrupting="true">
<errorEventDefinition/>
</startEvent>
<sequenceFlow id="handlerToEnd" sourceRef="errorHandlerStart" targetRef="errorHandlerEnd"/>
<endEvent id="errorHandlerEnd"/>
</subProcess>

</process>

<!-- Branch A's called process: raises a user task and waits. -->
<process id="waitingChild" isExecutable="true">
<startEvent id="childStart"/>
<sequenceFlow id="childToTask" sourceRef="childStart" targetRef="waitingTask"/>
<userTask id="waitingTask" name="Waiting Task" flowable:assignee="kermit"/>
<sequenceFlow id="childTaskToEnd" sourceRef="waitingTask" targetRef="childEnd"/>
<endEvent id="childEnd"/>
</process>
</definitions>