From 33f893be8c7ffb8a437c529b68f6c7d8925758fc Mon Sep 17 00:00:00 2001 From: "martin.grofcik" Date: Thu, 12 Mar 2015 17:01:35 +0100 Subject: [PATCH] TerminateEndEvent - extending activiti events. (cherry picked from commit 307f8ea) --- .../TerminateEndEventActivityBehavior.java | 80 +++++++---- .../impl/pvm/delegate/ActivityExecution.java | 2 +- .../impl/pvm/runtime/ExecutionImpl.java | 3 +- .../api/event/ProcessInstanceEventsTest.java | 129 +++++++++++++++--- ...ventTest.testTerminateInParentProcess.bpmn | 36 +++++ 5 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testTerminateInParentProcess.bpmn diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/TerminateEndEventActivityBehavior.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/TerminateEndEventActivityBehavior.java index 03ebeb19759..b7fe85e8cc0 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/TerminateEndEventActivityBehavior.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/TerminateEndEventActivityBehavior.java @@ -12,8 +12,10 @@ */ package org.activiti.engine.impl.bpmn.behavior; +import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder; import org.activiti.engine.impl.bpmn.helper.ScopeUtil; -import org.activiti.engine.impl.pvm.PvmActivity; +import org.activiti.engine.impl.context.Context; +import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.impl.pvm.runtime.InterpretableExecution; @@ -24,43 +26,61 @@ public class TerminateEndEventActivityBehavior extends FlowNodeActivityBehavior { public void execute(ActivityExecution execution) throws Exception { - - PvmActivity terminateEndEventActivity = execution.getActivity(); + ActivityImpl terminateEndEventActivity = (ActivityImpl) execution.getActivity(); ActivityExecution scopeExecution = ScopeUtil.findScopeExecution(execution); - + + // send cancelled event + sendCancelledEvent( execution, terminateEndEventActivity, scopeExecution); + // destroy the scope scopeExecution.destroyScope("terminate end event fired"); // set the scope execution to the terminate end event and make it end here. // (the history should reflect that the execution ended here and we want an 'end time' for the // historic activity instance.) - ((InterpretableExecution)scopeExecution).setActivity((ActivityImpl) terminateEndEventActivity); + ((InterpretableExecution)scopeExecution).setActivity(terminateEndEventActivity); // end the scope execution scopeExecution.end(); } - - - // If we use this implementation, we run into trouble in the DbSqlSession, see ACT-1382 - -// public void execute(ActivityExecution execution) throws Exception { -// -// PvmActivity terminateEndEventActivity = execution.getActivity(); -// -// ActivityExecution scopeExecution = ScopeUtil.findScopeExecution(execution); -// -// // first end the current execution normally -// execution.end(); -// -// // if this does not end the scope execution, interrupt it and destroy it. -// if (!scopeExecution.isEnded()) { -// // destroy the scope execution (this interrupts all child executions / sub process instances) -// scopeExecution.destroyScope("terminate end event fired"); -// -// // set the scope execution to the terminate end event make -// // (the history should reflect that the execution ended here). -// ((InterpretableExecution)scopeExecution).setActivity((ActivityImpl) terminateEndEventActivity); -// // end the scope execution -// scopeExecution.end(); -// } -// } + + private void sendCancelledEvent(ActivityExecution execution, ActivityImpl terminateEndEventActivity, ActivityExecution scopeExecution) { + if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) { + Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent( + ActivitiEventBuilder.createCancelledEvent(execution.getId(), execution.getProcessInstanceId(), + execution.getProcessDefinitionId(), terminateEndEventActivity)); + } + dispatchExecutionCancelled(scopeExecution, terminateEndEventActivity); + } + + private void dispatchExecutionCancelled(ActivityExecution execution, ActivityImpl causeActivity) { + // subprocesses + for (ActivityExecution subExecution : execution.getExecutions()) { + dispatchExecutionCancelled(subExecution, causeActivity); + } + + // call activities + ExecutionEntity subProcessInstance = Context.getCommandContext().getExecutionEntityManager().findSubProcessInstanceBySuperExecutionId(execution.getId()); + if (subProcessInstance != null) { + dispatchExecutionCancelled(subProcessInstance, causeActivity); + } + + // activity with message/signal boundary events + ActivityImpl activity = (ActivityImpl) execution.getActivity(); + if (activity != null && activity.getActivityBehavior() != null && activity != causeActivity) { + dispatchActivityCancelled(execution, activity, causeActivity); + } + } + + private void dispatchActivityCancelled(ActivityExecution execution, ActivityImpl activity, ActivityImpl causeActivity) { + Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent( + ActivitiEventBuilder.createActivityCancelledEvent(activity.getId(), + (String) activity.getProperties().get("name"), + execution.getId(), + execution.getProcessInstanceId(), execution.getProcessDefinitionId(), + (String) activity.getProperties().get("type"), + activity.getActivityBehavior().getClass().getCanonicalName(), + causeActivity) + ); + } + } diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/delegate/ActivityExecution.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/delegate/ActivityExecution.java index 192d2f3b720..235405e4a37 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/delegate/ActivityExecution.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/delegate/ActivityExecution.java @@ -144,5 +144,5 @@ public interface ActivityExecution extends DelegateExecution { * Performs destroy scope behavior: all child executions and sub-process instances and other related * resources are removed. The execution itself can continue execution. */ - void destroyScope(String string); + void destroyScope(String reason); } diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/runtime/ExecutionImpl.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/runtime/ExecutionImpl.java index 85cba588239..5d2b0ad0ea9 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/runtime/ExecutionImpl.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/pvm/runtime/ExecutionImpl.java @@ -201,7 +201,8 @@ public void remove() { } } } - + + @Override public void destroyScope(String reason) { log.debug("performing destroy scope behavior for execution {}", this); diff --git a/modules/activiti-engine/src/test/java/org/activiti/engine/test/api/event/ProcessInstanceEventsTest.java b/modules/activiti-engine/src/test/java/org/activiti/engine/test/api/event/ProcessInstanceEventsTest.java index 8c1e3d222dc..2a16ef07813 100755 --- a/modules/activiti-engine/src/test/java/org/activiti/engine/test/api/event/ProcessInstanceEventsTest.java +++ b/modules/activiti-engine/src/test/java/org/activiti/engine/test/api/event/ProcessInstanceEventsTest.java @@ -12,21 +12,24 @@ */ package org.activiti.engine.test.api.event; -import java.util.ArrayList; -import java.util.List; - -import org.activiti.engine.delegate.event.ActivitiActivityCancelledEvent; -import org.activiti.engine.delegate.event.ActivitiCancelledEvent; -import org.activiti.engine.delegate.event.ActivitiEntityEvent; -import org.activiti.engine.delegate.event.ActivitiEvent; -import org.activiti.engine.delegate.event.ActivitiEventListener; -import org.activiti.engine.delegate.event.ActivitiEventType; +import org.activiti.engine.delegate.event.*; +import org.activiti.engine.delegate.event.impl.ActivitiActivityCancelledEventImpl; +import org.activiti.engine.delegate.event.impl.ActivitiProcessCancelledEventImpl; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; +import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.impl.test.PluggableActivitiTestCase; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.engine.test.Deployment; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotEquals; + /** * Test case for all {@link ActivitiEvent}s related to process instances. * @@ -257,10 +260,9 @@ public void testProcessInstanceCancelledEvents_cancell() throws Exception { assertEquals("The execution instance has to be the same as in deleteProcessInstance method call", processInstance.getId(), activityCancelledEvent.getExecutionId()); assertEquals("The cause has to be the same as in deleteProcessInstance method call", "delete_test", activityCancelledEvent.getCause()); - listener.clearEventsReceived(); } - + @Deployment(resources = {"org/activiti/engine/test/api/runtime/nestedSubProcess.bpmn20.xml", "org/activiti/engine/test/api/runtime/subProcess.bpmn20.xml"}) public void testProcessInstanceCancelledEvents_cancelProcessHierarchy() throws Exception { @@ -268,9 +270,9 @@ public void testProcessInstanceCancelledEvents_cancelProcessHierarchy() throws E ProcessInstance subProcess = runtimeService.createProcessInstanceQuery().superProcessInstanceId(processInstance.getId()).singleResult(); assertNotNull(processInstance); listener.clearEventsReceived(); - + runtimeService.deleteProcessInstance(processInstance.getId(), "delete_test"); - + List processCancelledEvents = listener.filterEvents(ActivitiEventType.PROCESS_CANCELLED); assertEquals("ActivitiEventType.PROCESS_CANCELLED was expected 1 time.", 1, processCancelledEvents.size()); ActivitiCancelledEvent processCancelledEvent = (ActivitiCancelledEvent) processCancelledEvents.get(0); @@ -278,9 +280,9 @@ public void testProcessInstanceCancelledEvents_cancelProcessHierarchy() throws E assertEquals("The process instance has to be the same as in deleteProcessInstance method call", processInstance.getId(), processCancelledEvent.getProcessInstanceId()); assertEquals("The execution instance has to be the same as in deleteProcessInstance method call", processInstance.getId(), processCancelledEvent.getExecutionId()); assertEquals("The cause has to be the same as in deleteProcessInstance method call", "delete_test", processCancelledEvent.getCause()); - + assertEquals("No task can be active for deleted process.", 0, this.taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()); - + List taskCancelledEvents = listener.filterEvents(ActivitiEventType.ACTIVITY_CANCELLED); assertEquals("ActivitiEventType.ACTIVITY_CANCELLED was expected 1 time.", 1, taskCancelledEvents.size()); ActivitiActivityCancelledEvent activityCancelledEvent = (ActivitiActivityCancelledEvent) taskCancelledEvents.get(0); @@ -289,7 +291,8 @@ public void testProcessInstanceCancelledEvents_cancelProcessHierarchy() throws E assertEquals("The process instance has to point to the subprocess", subProcess.getId(), activityCancelledEvent.getProcessInstanceId()); assertEquals("The execution instance has to point to the subprocess", subProcess.getId(), activityCancelledEvent.getExecutionId()); assertEquals("The cause has to be the same as in deleteProcessInstance method call", "delete_test", activityCancelledEvent.getCause()); - + + listener.clearEventsReceived(); } @@ -308,6 +311,100 @@ public void testProcessInstanceCancelledEvents_complete() throws Exception { } + @Deployment(resources = {"org/activiti/engine/test/api/runtime/oneTaskProcess.bpmn20.xml"}) + public void testProcessInstanceTerminatedEvents_complete() throws Exception { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess"); + assertNotNull(processInstance); + + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + taskService.complete(task.getId()); + + List processTerminatedEvents = listener.filterEvents(ActivitiEventType.PROCESS_CANCELLED); + assertEquals("There should be no ActivitiEventType.PROCESS_TERMINATED event after process complete.", 0, processTerminatedEvents.size()); + } + + @Deployment(resources="org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testProcessTerminate.bpmn") + public void testProcessInstanceTerminatedEvents() throws Exception { + ProcessInstance pi = runtimeService.startProcessInstanceByKey("terminateEndEventExample"); + + long executionEntities = runtimeService.createExecutionQuery().processInstanceId(pi.getId()).count(); + assertEquals(3, executionEntities); + + Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("preTerminateTask").singleResult(); + taskService.complete(task.getId()); + + List processTerminatedEvents = listener.filterEvents(ActivitiEventType.PROCESS_CANCELLED); + assertEquals("There should be exactly one ActivitiEventType.PROCESS_CANCELLED event after the task complete.", 1, processTerminatedEvents.size()); + ActivitiProcessCancelledEventImpl activitiEvent = (ActivitiProcessCancelledEventImpl) processTerminatedEvents.get(0); + assertThat(activitiEvent.getProcessInstanceId(), is(pi.getProcessInstanceId())); + assertThat(((ActivityImpl) activitiEvent.getCause()).getId(), is("EndEvent_2")); + + List activityTerminatedEvents = listener.filterEvents(ActivitiEventType.ACTIVITY_CANCELLED); + assertThat("There should be exactly two ActivitiEventType.ACTIVITY_CANCELLED event after the task complete.", activityTerminatedEvents.size(), is(2)); + ActivitiActivityCancelledEventImpl activityEvent = (ActivitiActivityCancelledEventImpl) activityTerminatedEvents.get(0); + assertThat("The user task must be terminated", activityEvent.getActivityId(), is("preNormalTerminateTask")); + assertThat("The cause must be terminate end event", ((ActivityImpl) activityEvent.getCause()).getId(), is("EndEvent_2")); + activityEvent = (ActivitiActivityCancelledEventImpl) activityTerminatedEvents.get(1); + assertThat("The gateway must be terminated", activityEvent.getActivityId(), is("ParallelGateway_1")); + assertThat("The cause must be terminate end event", ((ActivityImpl) activityEvent.getCause()).getId(), is("EndEvent_2")); + } + + @Deployment(resources = { + "org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testTerminateInCallActivity.bpmn", + "org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.subProcessTerminate.bpmn" + }) + public void testProcessInstanceTerminatedEvents_callActivity() throws Exception { + ProcessInstance pi = runtimeService.startProcessInstanceByKey("terminateEndEventExample"); + + // should terminate the called process and continue the parent + long executionEntities = runtimeService.createExecutionQuery().count(); + assertEquals(1, executionEntities); + + Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("preNormalEnd").singleResult(); + taskService.complete(task.getId()); + + assertProcessEnded(pi.getId()); + List processTerminatedEvents = listener.filterEvents(ActivitiEventType.PROCESS_CANCELLED); + assertEquals("There should be exactly one ActivitiEventType.PROCESS_TERMINATED event after the task complete.", 1, processTerminatedEvents.size()); + ActivitiProcessCancelledEventImpl processCancelledEvent = (ActivitiProcessCancelledEventImpl) processTerminatedEvents.get(0); + assertNotEquals(pi.getProcessInstanceId(), processCancelledEvent.getProcessInstanceId()); + assertThat(processCancelledEvent.getProcessDefinitionId(), containsString("terminateEndEventSubprocessExample")); + + List activityTerminatedEvents = listener.filterEvents(ActivitiEventType.ACTIVITY_CANCELLED); + assertThat("There is no ActivitiEventType.ACTIVITY_CANCELLED event after the task complete.", activityTerminatedEvents.isEmpty(), is(true)); + } + + @Deployment(resources = { + "org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testTerminateInParentProcess.bpmn", + "org/activiti/engine/test/api/runtime/oneTaskProcess.bpmn20.xml" + }) + public void testProcessInstanceTerminatedEvents_terminateInParentProcess() throws Exception { + ProcessInstance pi = runtimeService.startProcessInstanceByKey("terminateParentProcess"); + + // should terminate the called process and continue the parent + Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).taskDefinitionKey("preTerminateEnd").singleResult(); + taskService.complete(task.getId()); + + assertProcessEnded(pi.getId()); + List processTerminatedEvents = listener.filterEvents(ActivitiEventType.PROCESS_CANCELLED); + assertEquals("There should be exactly one ActivitiEventType.PROCESS_TERMINATED event after the task complete.", 1, processTerminatedEvents.size()); + ActivitiProcessCancelledEventImpl processCancelledEvent = (ActivitiProcessCancelledEventImpl) processTerminatedEvents.get(0); + assertThat(processCancelledEvent.getProcessInstanceId(), is(pi.getProcessInstanceId())); + assertThat(processCancelledEvent.getProcessDefinitionId(), containsString("terminateParentProcess")); + + List activityTerminatedEvents = listener.filterEvents(ActivitiEventType.ACTIVITY_CANCELLED); + assertThat("3 activities must be cancelled.", activityTerminatedEvents.size(), is(3)); + ActivitiActivityCancelledEventImpl activityEvent = (ActivitiActivityCancelledEventImpl) activityTerminatedEvents.get(0); + assertThat("The user task must be terminated in the called sub process.", activityEvent.getActivityId(), is("theTask")); + assertThat("The cause must be terminate end event", ((ActivityImpl) activityEvent.getCause()).getId(), is("EndEvent_3")); + activityEvent = (ActivitiActivityCancelledEventImpl) activityTerminatedEvents.get(1); + assertThat("The call activity must be terminated", activityEvent.getActivityId(), is("CallActivity_1")); + assertThat("The cause must be terminate end event", ((ActivityImpl) activityEvent.getCause()).getId(), is("EndEvent_3")); + activityEvent = (ActivitiActivityCancelledEventImpl) activityTerminatedEvents.get(2); + assertThat("The gateway must be terminated", activityEvent.getActivityId(), is("ParallelGateway_1")); + assertThat("The cause must be terminate end event", ((ActivityImpl) activityEvent.getCause()).getId(), is("EndEvent_3")); + } + @Override protected void initializeServices() { super.initializeServices(); diff --git a/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testTerminateInParentProcess.bpmn b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testTerminateInParentProcess.bpmn new file mode 100644 index 00000000000..cdf388a563a --- /dev/null +++ b/modules/activiti-engine/src/test/resources/org/activiti/engine/test/bpmn/event/end/TerminateEndEventTest.testTerminateInParentProcess.bpmn @@ -0,0 +1,36 @@ + + + + + SequenceFlow_2 + + + SequenceFlow_2 + SequenceFlow_4 + SequenceFlow_6 + + + + SequenceFlow_4 + SequenceFlow_1 + + + + SequenceFlow_7 + + + SequenceFlow_6 + SequenceFlow_7 + + + + + SequenceFlow_1 + + + + + \ No newline at end of file