diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/ChangeActivityStateCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/ChangeActivityStateCmd.java index cf975a57932..1832260796e 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/ChangeActivityStateCmd.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/ChangeActivityStateCmd.java @@ -34,9 +34,10 @@ public ChangeActivityStateCmd(ChangeActivityStateBuilderImpl changeActivityState @Override public Void execute(CommandContext commandContext) { if (changeActivityStateBuilder.getMoveExecutionIdList().isEmpty() && changeActivityStateBuilder.getMoveActivityIdList().isEmpty() - && changeActivityStateBuilder.getEnableActivityIdList().isEmpty()) { + && changeActivityStateBuilder.getEnableActivityIdList().isEmpty() && changeActivityStateBuilder.getTerminateActivityIdList().isEmpty() + && changeActivityStateBuilder.getTerminateExecutionIdList().isEmpty()) { - throw new FlowableIllegalArgumentException("No move execution or activity ids or enable activity ids provided"); + throw new FlowableIllegalArgumentException("No move, enable or terminate activity ids provided"); } else if ((!changeActivityStateBuilder.getMoveActivityIdList().isEmpty() || !changeActivityStateBuilder.getEnableActivityIdList().isEmpty()) && changeActivityStateBuilder.getProcessInstanceId() == null) { throw new FlowableIllegalArgumentException("Process instance id is required"); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java index 63f6140142b..5aaf45401fe 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java @@ -81,6 +81,8 @@ import org.flowable.engine.impl.runtime.EnableActivityIdContainer; import org.flowable.engine.impl.runtime.MoveActivityIdContainer; import org.flowable.engine.impl.runtime.MoveExecutionIdContainer; +import org.flowable.engine.impl.runtime.TerminateActivityContainer; +import org.flowable.engine.impl.runtime.TerminateExecutionContainer; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.CountingEntityUtil; import org.flowable.engine.impl.util.EntityLinkUtil; @@ -248,7 +250,66 @@ public List resolveMoveExecutionEntityContainers(C return moveExecutionEntityContainerList; } - + + public List resolveTerminateActivityContainers(ChangeActivityStateBuilderImpl changeActivityStateBuilder, CommandContext commandContext) { + List terminateActivityContainerList = new ArrayList<>(); +// if (!changeActivityStateBuilder.getTerminateActivityIdList().isEmpty()) { +// for (TerminateActivityContainer terminateActivityIdContainer : changeActivityStateBuilder.getTerminateActivityIdList()) { +// TerminateActivityContainer terminateActivityContainer = new TerminateActivityContainer(terminateActivityIdContainer.getActivityIds()); +// terminateActivityContainerList.add(terminateActivityContainer); +// resolveActiveExecution(executionId, commandContext) +// terminateActivityContainer.setExecution(terminateActivityIdContainer.getExecution()); +// } +// } + + return terminateActivityContainerList; + } + + public List resolveTerminateExecutionContainers(ChangeActivityStateBuilderImpl changeActivityStateBuilder, CommandContext commandContext) { + List terminateExecutionContainerList = new ArrayList<>(); + if (!changeActivityStateBuilder.getTerminateExecutionIdList().isEmpty()) { + for (TerminateExecutionContainer terminateExecutionIdContainer : changeActivityStateBuilder.getTerminateExecutionIdList()) { + TerminateExecutionContainer terminateExecutionContainer = new TerminateExecutionContainer(terminateExecutionIdContainer.getExecutionIds()); + for (String executionId : terminateExecutionIdContainer.getExecutionIds()) { + terminateExecutionContainer.getExecutions().add(resolveActiveExecution(executionId, commandContext)); + } + terminateExecutionContainerList.add(terminateExecutionContainer); + } + } + + return terminateExecutionContainerList; + } + + public List resolveExecutionsFromTerminateActivitiesContainers(ChangeActivityStateBuilderImpl changeActivityStateBuilder, CommandContext commandContext) { + List terminateExecutionContainerList = new ArrayList<>(); + if(changeActivityStateBuilder.getTerminateActivityIdList().isEmpty()) { + return terminateExecutionContainerList; + } + + String processInstanceId = changeActivityStateBuilder.getProcessInstanceId(); + ExecutionEntity execution = resolveActiveExecution(processInstanceId, commandContext); + ExecutionEntity finalExecution; + if (!changeActivityStateBuilder.getTerminateActivityIdList().isEmpty()) { + for (TerminateActivityContainer terminateActivityIdContainer : changeActivityStateBuilder.getTerminateActivityIdList()) { + if(terminateActivityIdContainer.isTerminateInParentProcess()) { + ExecutionEntity processInstanceExecution = execution.getProcessInstance(); + finalExecution = processInstanceExecution.getSuperExecution(); + if (finalExecution == null) { + throw new FlowableException("No parent process found for execution with activity id " + execution.getCurrentActivityId()); + } + } + + TerminateExecutionContainer terminateExecutionContainer = new TerminateExecutionContainer(); + for (String activityId : terminateActivityIdContainer.getActivityIds()) { + terminateExecutionContainer.getExecutions().addAll(resolveActiveExecutions(processInstanceId, activityId, commandContext)); + } + terminateExecutionContainerList.add(terminateExecutionContainer); + } + } + + return terminateExecutionContainerList; + } + public List resolveEnableActivityContainers(ChangeActivityStateBuilderImpl changeActivityStateBuilder) { List enableActivityContainerList = new ArrayList<>(); if (!changeActivityStateBuilder.getEnableActivityIdList().isEmpty()) { @@ -651,6 +712,21 @@ protected void doMoveExecutionState(ProcessInstanceChangeState processInstanceCh } } } + + + // Terminate executions + for(TerminateExecutionContainer terminateExecutionContainer : processInstanceChangeState.getTerminateExecutionContainers()) { + if (terminateExecutionContainer.getExecutions() != null && !terminateExecutionContainer.getExecutions().isEmpty()) { + for (ExecutionEntity execution : terminateExecutionContainer.getExecutions()) { + if(execution == null) { + throw new FlowableException("Execution cannot be terminate because it is null"); + } + executionEntityManager.deleteChildExecutions(execution, Collections.emptyList(), null, "Terminate execution " + execution.getId() + " with activity id " + execution.getActivityId(), true, null); + executionEntityManager.deleteExecutionAndRelatedData(execution, "Terminate execution " + execution.getId() + " with activity id " + execution.getActivityId(), false, false, true, execution.getCurrentFlowElement()); + } + } + } + } protected void processPendingEventSubProcessesStartEvents(ProcessInstanceChangeState processInstanceChangeState, CommandContext commandContext) { @@ -673,6 +749,7 @@ protected void safeDeleteSubProcessInstance(String processInstanceId, List subProcessExecutions = executionEntityManager.findChildExecutionsByProcessInstanceId(processInstanceId); + subProcessExecutions = subProcessExecutions.stream().filter(e -> !(e.getCurrentFlowElement() instanceof BoundaryEvent)).collect(Collectors.toList()); HashSet executionIdsToMove = executionsPool.stream().map(ExecutionEntity::getId).collect(Collectors.toCollection(HashSet::new)); Optional notIncludedExecution = subProcessExecutions.stream().filter(e -> !executionIdsToMove.contains(e.getId())).findAny(); if (notIncludedExecution.isPresent()) { diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/DefaultDynamicStateManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/DefaultDynamicStateManager.java index 74921ebce50..4ae42b0ddea 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/DefaultDynamicStateManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/DefaultDynamicStateManager.java @@ -13,6 +13,7 @@ package org.flowable.engine.impl.dynamic; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -24,6 +25,7 @@ import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; import org.flowable.engine.impl.runtime.ChangeActivityStateBuilderImpl; +import org.flowable.engine.impl.runtime.TerminateExecutionContainer; import org.flowable.engine.impl.util.CommandContextUtil; /** @@ -47,14 +49,19 @@ public void moveExecutionState(ChangeActivityStateBuilderImpl changeActivityStat } else { processInstanceId = changeActivityStateBuilder.getProcessInstanceId(); } - + + List terminateExecutionContainerList = new ArrayList<>(); + terminateExecutionContainerList.addAll(resolveTerminateExecutionContainers(changeActivityStateBuilder, commandContext)); + terminateExecutionContainerList.addAll(resolveExecutionsFromTerminateActivitiesContainers(changeActivityStateBuilder, commandContext)); + ProcessInstanceChangeState processInstanceChangeState = new ProcessInstanceChangeState() .setProcessInstanceId(processInstanceId) .setMoveExecutionEntityContainers(moveExecutionEntityContainerList) .setEnableActivityContainers(enableActivityContainerList) + .setTerminateExecutionContainers(terminateExecutionContainerList) .setLocalVariables(changeActivityStateBuilder.getLocalVariables()) .setProcessInstanceVariables(changeActivityStateBuilder.getProcessInstanceVariables()); - + doMoveExecutionState(processInstanceChangeState, commandContext); } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/ProcessInstanceChangeState.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/ProcessInstanceChangeState.java index aaca70aae90..772f3603a0d 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/ProcessInstanceChangeState.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/ProcessInstanceChangeState.java @@ -19,6 +19,7 @@ import org.flowable.bpmn.model.StartEvent; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.runtime.TerminateExecutionContainer; import org.flowable.engine.repository.ProcessDefinition; /** @@ -33,6 +34,7 @@ public class ProcessInstanceChangeState { protected Map> localVariables = new HashMap<>(); protected Map> processInstanceActiveEmbeddedExecutions; protected List moveExecutionEntityContainers; + protected List terminateExecutionContainers; protected List enableActivityContainers; protected HashMap createdEmbeddedSubProcess = new HashMap<>(); protected HashMap createdMultiInstanceRootExecution = new HashMap<>(); @@ -99,6 +101,16 @@ public ProcessInstanceChangeState setMoveExecutionEntityContainers(List getTerminateExecutionContainers() { + return terminateExecutionContainers; + } + + public ProcessInstanceChangeState setTerminateExecutionContainers(List terminateExecutionContainerList) { + this.terminateExecutionContainers = terminateExecutionContainerList; + return this; + } + + public HashMap getCreatedEmbeddedSubProcesses() { return createdEmbeddedSubProcess; } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/ChangeActivityStateBuilderImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/ChangeActivityStateBuilderImpl.java index 0aeb7bdd093..6c6f3037f19 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/ChangeActivityStateBuilderImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/ChangeActivityStateBuilderImpl.java @@ -32,6 +32,8 @@ public class ChangeActivityStateBuilderImpl implements ChangeActivityStateBuilde protected List moveExecutionIdList = new ArrayList<>(); protected List moveActivityIdList = new ArrayList<>(); protected List enableActivityIdList = new ArrayList<>(); + protected List terminateActivityContainers = new ArrayList<>(); + protected List terminateExecutionContainers = new ArrayList<>(); protected Map processVariables = new HashMap<>(); protected Map> localVariables = new HashMap<>(); @@ -171,7 +173,7 @@ public ChangeActivityStateBuilder moveSingleActivityIdToSubProcessInstanceActivi moveActivityIdList.add(moveActivityIdsContainer); return this; } - + @Override public ChangeActivityStateBuilder enableEventSubProcessStartEvent(String eventSubProcessStartEventId) { EnableActivityIdContainer enableActivityIdContainer = new EnableActivityIdContainer(eventSubProcessStartEventId); @@ -179,6 +181,53 @@ public ChangeActivityStateBuilder enableEventSubProcessStartEvent(String eventSu return this; } + @Override + public ChangeActivityStateBuilder terminateActivity(String activityId) { + TerminateActivityContainer terminateActivityContainer = new TerminateActivityContainer(activityId); + terminateActivityContainers.add(terminateActivityContainer); + return this; + } + + @Override + public ChangeActivityStateBuilder terminateActivities(List activityIds) { + TerminateActivityContainer terminateActivityContainer = new TerminateActivityContainer(activityIds); + terminateActivityContainers.add(terminateActivityContainer); + return this; + } + + @Override + public ChangeActivityStateBuilder terminateParentProcessInstanceActivity(String activityId) { + TerminateActivityContainer terminateActivityContainer = new TerminateActivityContainer(activityId); + terminateActivityContainer.setTerminateInParentProcess(true); + terminateActivityContainers.add(terminateActivityContainer); + return this; + } + + + @Override + public ChangeActivityStateBuilder terminateParentProcessInstanceActivities(List activityIds) { + TerminateActivityContainer terminateActivityContainer = new TerminateActivityContainer(activityIds); + terminateActivityContainer.setTerminateInParentProcess(true); + terminateActivityContainers.add(terminateActivityContainer); + return this; + } + + + + @Override + public ChangeActivityStateBuilder terminateExecution(String executionId) { + TerminateExecutionContainer terminateExecutionContainer = new TerminateExecutionContainer(executionId); + terminateExecutionContainers.add(terminateExecutionContainer); + return this; + } + + @Override + public ChangeActivityStateBuilder terminateExecutions(List executionIds) { + TerminateExecutionContainer terminateExecutionContainer = new TerminateExecutionContainer(executionIds); + terminateExecutionContainers.add(terminateExecutionContainer); + return this; + } + @Override public ChangeActivityStateBuilder processVariable(String processVariableName, Object processVariableValue) { if (this.processVariables == null) { @@ -250,6 +299,14 @@ public List getEnableActivityIdList() { return enableActivityIdList; } + public List getTerminateActivityIdList() { + return terminateActivityContainers; + } + + public List getTerminateExecutionIdList() { + return terminateExecutionContainers; + } + public Map getProcessInstanceVariables() { return processVariables; } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/TerminateActivityContainer.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/TerminateActivityContainer.java new file mode 100644 index 00000000000..ebbe94ca7ce --- /dev/null +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/TerminateActivityContainer.java @@ -0,0 +1,47 @@ +/* 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.impl.runtime; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author Matthias Stöckli + */ +public class TerminateActivityContainer { + + protected List activityIds; + + protected boolean terminateInParentProcess; + + public TerminateActivityContainer(String singleActivityId) { + this.activityIds = Collections.singletonList(singleActivityId); + } + + public TerminateActivityContainer(List activityIds) { + this.activityIds = activityIds; + } + + public List getActivityIds() { + return Optional.ofNullable(activityIds).orElse(Collections.emptyList()); + } + + public boolean isTerminateInParentProcess() { + return terminateInParentProcess; + } + + public void setTerminateInParentProcess(boolean terminateInParentProcess) { + this.terminateInParentProcess = terminateInParentProcess; + } +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/TerminateExecutionContainer.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/TerminateExecutionContainer.java new file mode 100644 index 00000000000..7a98cb2aa8e --- /dev/null +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/TerminateExecutionContainer.java @@ -0,0 +1,50 @@ +/* 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.impl.runtime; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; + +/** + * @author Matthias Stöckli + */ +public class TerminateExecutionContainer { + + protected List executionIds = new ArrayList<>(); + protected List executions = new ArrayList<>(); + + public TerminateExecutionContainer() { + } + + public TerminateExecutionContainer(String singleExecutionId) { + this.executionIds = Collections.singletonList(singleExecutionId); + } + + public TerminateExecutionContainer(List executionIds) { + this.executionIds = executionIds; + } + + public List getExecutionIds() { + return Optional.ofNullable(executionIds).orElse(Collections.emptyList()); + } + + + public List getExecutions() { + return executions; + } +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/runtime/ChangeActivityStateBuilder.java b/modules/flowable-engine/src/main/java/org/flowable/engine/runtime/ChangeActivityStateBuilder.java index 37e8311b7ca..208a914f85e 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/runtime/ChangeActivityStateBuilder.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/runtime/ChangeActivityStateBuilder.java @@ -87,6 +87,36 @@ public interface ChangeActivityStateBuilder { */ ChangeActivityStateBuilder enableEventSubProcessStartEvent(String eventSubProcessStartEventId); + /** + * Terminates a specific activity. + */ + ChangeActivityStateBuilder terminateActivity(String activityId); + + /** + * Terminates a list of activities. + */ + ChangeActivityStateBuilder terminateActivities(List activityIds); + + /** + * Terminates a specific activity in the parent process instance. + */ + ChangeActivityStateBuilder terminateParentProcessInstanceActivity(String activityId); + + /** + * Terminate a list of activities in the parent process instance. + */ + ChangeActivityStateBuilder terminateParentProcessInstanceActivities(List activityIds); + + /** + * Terminates a specific activity in the parent process instance. + */ + ChangeActivityStateBuilder terminateExecution(String executionId); + + /** + * Terminates a list of executions. + */ + ChangeActivityStateBuilder terminateExecutions(List executionIds); + /** * Sets a process scope variable */ diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateTest.java index babdf8f6a65..13045356eac 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/changestate/ChangeStateTest.java @@ -20,11 +20,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEvent; import org.flowable.engine.delegate.event.FlowableActivityEvent; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; import org.flowable.engine.impl.test.PluggableFlowableTestCase; import org.flowable.engine.runtime.ChangeActivityStateBuilder; import org.flowable.engine.runtime.DataObject; @@ -3404,22 +3406,22 @@ public void testEnableEventSubProcessStartEvent() { Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); assertThat(task.getTaskDefinitionKey()).isEqualTo("processTask"); - + runtimeService.signalEventReceived("mySignal"); - + task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); assertThat(task.getTaskDefinitionKey()).isEqualTo("eventSubProcessTask"); - + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(0); runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getId()) .enableEventSubProcessStartEvent("messageEventSubProcessStart") .changeState(); - + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(1); EventSubscription messageEventSubscription = runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).singleResult(); - + runtimeService.messageEventReceived("myMessage", messageEventSubscription.getExecutionId()); task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); @@ -3428,5 +3430,236 @@ public void testEnableEventSubProcessStartEvent() { assertProcessEnded(processInstance.getId()); } + + @Test + @Deployment(resources = { "org/flowable/engine/test/api/runtime/changestate/boundaryEventStateChange.bpmn20.xml", "org/flowable/engine/test/api/runtime/changestate/boundaryEventSubproceess.bpmn20.xml"}) + public void testSkipEventListener() { + // Set up: One user task, a parallel gateway leading to a task and a call activity. + // Once we reach the call activity, we want to return to the parent execution's first task. + + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("boundary_event_state_change"); + // Complete first task + Task task1 = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + taskService.complete(task1.getId()); + + ProcessInstance newProcessInstance = runtimeService.createProcessInstanceQuery().superProcessInstanceId(processInstance.getId()).singleResult(); + Task task2 = taskService.createTaskQuery().processInstanceIdWithChildren(newProcessInstance.getId()).taskDefinitionKey("task_in_call_activity").singleResult(); + + // + List tasks = taskService.createTaskQuery().processInstanceIdWithChildren(processInstance.getId()).list(); + assertThat(tasks).hasSize(2); + assertThat(tasks).element(0).extracting(Task::getTaskDefinitionKey).isEqualTo("another_task"); + assertThat(tasks).element(1).extracting(Task::getTaskDefinitionKey).isEqualTo("task_in_call_activity"); + + + // Move back to the first task in parent execution AND set variable + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(newProcessInstance.getId()) + .moveActivityIdToParentActivityId(task2.getTaskDefinitionKey(), task1.getTaskDefinitionKey()) + .processVariable("myVariable", "test") + .changeState(); + + } + + @Test + @Deployment(resources = { + "org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.testTerminateExecution.bpmn20.xml", + "org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocess.bpmn20.xml", + "org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocessMI.bpmn20.xml", + }) + public void testTerminateExecution() { + ////////////////////////////////////////// + // Single executions / user tasks + ////////////////////////////////////////// + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("terminateExecution"); + + List executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(3); + + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateExecution(executions.get(0).getId()) + .changeState(); + + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(2); + + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateExecutions(List.of(executions.get(0).getId(), executions.get(1).getId())) + .changeState(); + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + + assertThat(executions).hasSize(0); + + // Subprocess (no MI), terminate subprocess and check that the user task within is no longer available + processInstance = runtimeService.startProcessInstanceByKey("terminateExecutionSubprocess"); + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + + // check that one is called subprocess, one is called task_1 and the other one task_2 + assertThat(executions).hasSize(3); + assertThat(executions.stream().map(Execution::getActivityId).collect(Collectors.toList())).containsExactlyInAnyOrder("subprocess", "subprocess_task", "user_task"); + + String subprocessExecutionId = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).activityId("subprocess").onlyChildExecutions().singleResult().getId(); + + // Terminate the subprocess, the user task within should no longer be available as well then + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateExecution(subprocessExecutionId) + .changeState(); + + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(1); + assertThat(executions).singleElement().extracting(Execution::getActivityId).isEqualTo("user_task"); + + ////////////////////////////////////////// + // Subprocess (no MI), terminate user task within subprocess and check that the subprocess within is no longer available + ////////////////////////////////////////// + processInstance = runtimeService.startProcessInstanceByKey("terminateExecutionSubprocess"); + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + + // check that one is called subprocess, one is called task_1 and the other one task_2 + assertThat(executions).hasSize(3); + assertThat(executions.stream().map(Execution::getActivityId).collect(Collectors.toList())).containsExactlyInAnyOrder("subprocess", "subprocess_task", "user_task"); + + String subprocessTaskExecutionId = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).activityId("subprocess_task").onlyChildExecutions().singleResult().getId(); + + // Terminate the subprocess, the user task within should no longer be available as well then + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateExecution(subprocessTaskExecutionId) + .changeState(); + + // TODO: This does not yet work, subprocess is not closed + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); +// assertThat(executions).hasSize(1); +// assertThat(executions).singleElement().extracting(Execution::getActivityId).isEqualTo("user_task"); + + ////////////////////////////////////////// + // Subprocess (parallel MI), terminate subprocess and check that the 5 tasks are no longer available + ////////////////////////////////////////// + processInstance = runtimeService.startProcessInstanceByKey("terminateExecutionSubprocessMI"); + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + + // check that we have: + // 1 root execution for mi subprocesses + // 5 executions for the subprocess (cardinality 5) + // 5 tasks + // 1 task outside of the process + assertThat(executions).hasSize(12); + assertThat(executions.stream().map(Execution::getActivityId).collect(Collectors.toList())).contains("parallel_mi_subprocess", "task_1", "subprocess_task"); + + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).list(); + String rootSubprocessExecutionId = executions.stream().filter(e -> ((ExecutionEntityImpl)e).isMultiInstanceRoot()).findFirst().get().getId(); + + // Terminate the root of the multi-instance subprocess + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateExecution(rootSubprocessExecutionId) + .changeState(); + + // There should be just 1 execution left + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(1); + assertThat(executions).singleElement().extracting(Execution::getActivityId).isEqualTo("task_1"); + } + + @Test + @Deployment(resources = { + "org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.testTerminateExecution.bpmn20.xml", + "org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocess.bpmn20.xml", + "org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocessMI.bpmn20.xml", + }) + public void testTerminateActitivies() { + ////////////////////////////////////////// + // Single activities / user tasks + ////////////////////////////////////////// + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("terminateExecution"); + + List executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(3); + + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateActivity("task_1") + .changeState(); + + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(2); + + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateActivities(List.of("task_2", "task_3")) + .changeState(); + + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(0); + + // Subprocess (no MI), terminate subprocess and check that the user task within is no longer available + processInstance = runtimeService.startProcessInstanceByKey("terminateExecutionSubprocess"); + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + + // check that one is called subprocess, one is called task_1 and the other one task_2 + assertThat(executions).hasSize(3); + assertThat(executions.stream().map(Execution::getActivityId).collect(Collectors.toList())).containsExactlyInAnyOrder("subprocess", "subprocess_task", "user_task"); + + // Terminate the subprocess, the user task within should no longer be available as well then + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateActivity("subprocess") + .changeState(); + + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(1); + assertThat(executions).singleElement().extracting(Execution::getActivityId).isEqualTo("user_task"); + + ////////////////////////////////////////// + // Subprocess (no MI), terminate user task within subprocess and check that the subprocess within is no longer available + ////////////////////////////////////////// + processInstance = runtimeService.startProcessInstanceByKey("terminateExecutionSubprocess"); + + // check that one is called subprocess, one is called task_1 and the other one task_2 + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(3); + assertThat(executions.stream().map(Execution::getActivityId).collect(Collectors.toList())).containsExactlyInAnyOrder("subprocess", "subprocess_task", "user_task"); + + // Terminate the subprocess, the user task within should no longer be available as well then + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateActivity("subprocess_task") + .changeState(); + + // TODO: This does not yet work, subprocess is not closed + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); +// assertThat(executions).hasSize(1); +// assertThat(executions).singleElement().extracting(Execution::getActivityId).isEqualTo("user_task"); + + ////////////////////////////////////////// + // Subprocess (parallel MI), terminate subprocess and check that the 5 tasks are no longer available + ////////////////////////////////////////// + processInstance = runtimeService.startProcessInstanceByKey("terminateExecutionSubprocessMI"); + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + + // check that we have: + // 1 root execution for mi subprocesses + // 5 executions for the subprocess (cardinality 5) + // 5 tasks + // 1 task outside of the process + assertThat(executions).hasSize(12); + assertThat(executions.stream().map(Execution::getActivityId).collect(Collectors.toList())).contains("parallel_mi_subprocess", "task_1", "subprocess_task"); + + // Terminate the root of the multi-instance subprocess + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getId()) + .terminateActivity("parallel_mi_subprocess") + .changeState(); + + // There should be just 1 activity left + executions = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).onlyChildExecutions().list(); + assertThat(executions).hasSize(1); + assertThat(executions).singleElement().extracting(Execution::getActivityId).isEqualTo("task_1"); + + // TODO: terminateParentProcessInstanceActivity, terminateParentProcessInstanceActivities + } } diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocess.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocess.bpmn20.xml new file mode 100644 index 00000000000..7caccfaa8e0 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocess.bpmn20.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocessMI.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocessMI.bpmn20.xml new file mode 100644 index 00000000000..791bb95c8c4 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.terminateExecutionSubprocessMI.bpmn20.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.testTerminateExecution.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.testTerminateExecution.bpmn20.xml new file mode 100644 index 00000000000..1c45e8b2221 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/RuntimeServiceChangeStateTest.testTerminateExecution.bpmn20.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/boundaryEventStateChange.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/boundaryEventStateChange.bpmn20.xml new file mode 100644 index 00000000000..3b6e0f3becf --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/boundaryEventStateChange.bpmn20.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/boundaryEventSubproceess.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/boundaryEventSubproceess.bpmn20.xml new file mode 100644 index 00000000000..885320d87bf --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/runtime/changestate/boundaryEventSubproceess.bpmn20.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file