diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/SignalEventTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/SignalEventTest.java index b87785ecf56..983799a439c 100644 --- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/SignalEventTest.java +++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/SignalEventTest.java @@ -44,16 +44,13 @@ public void testSignal() { assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); Task task = processEngineTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance.getId()).singleResult(); - assertThat(task).isNotNull(); - assertThat(task.getTaskDefinitionKey()).isEqualTo("theTask"); - assertThat(task.getName()).isEqualTo("my task"); + assertThatCaseTaskWasCreated(task); - EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); - assertThat(eventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(caseInstance); processEngineTaskService.complete(task.getId()); - eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); + EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); assertThat(eventSubscription).isNull(); Task task2 = processEngineTaskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); @@ -94,22 +91,18 @@ public void testMultipleSignals() { assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); Task task = processEngineTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance.getId()).singleResult(); - assertThat(task).isNotNull(); - assertThat(task.getTaskDefinitionKey()).isEqualTo("theTask"); - assertThat(task.getName()).isEqualTo("my task"); + assertThatCaseTaskWasCreated(task); - EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); - assertThat(eventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(caseInstance); - EventSubscription anotherEventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(anotherCaseInstance.getId()).singleResult(); - assertThat(anotherEventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(anotherCaseInstance); processEngineTaskService.complete(task.getId()); - eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); + EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); assertThat(eventSubscription).isNull(); - anotherEventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(anotherCaseInstance.getId()).singleResult(); + EventSubscription anotherEventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(anotherCaseInstance.getId()).singleResult(); assertThat(anotherEventSubscription).isNull(); Task task2 = processEngineTaskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); @@ -152,16 +145,13 @@ public void testSignalWithInstanceScope() { assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); Task task = processEngineTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance.getId()).singleResult(); - assertThat(task).isNotNull(); - assertThat(task.getTaskDefinitionKey()).isEqualTo("theTask"); - assertThat(task.getName()).isEqualTo("my task"); + assertThatCaseTaskWasCreated(task); - EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); - assertThat(eventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(caseInstance); processEngineTaskService.complete(task.getId()); - eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); + EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); assertThat(eventSubscription).isNull(); Task task2 = processEngineTaskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); @@ -201,19 +191,17 @@ public void testMultipleSignalWithInstanceScope() { assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); - EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); - assertThat(eventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(caseInstance); - EventSubscription anotherEventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(anotherCaseInstance.getId()).singleResult(); - assertThat(anotherEventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(anotherCaseInstance); Task task = processEngineTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance.getId()).singleResult(); processEngineTaskService.complete(task.getId()); - eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); + EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); assertThat(eventSubscription).isNull(); - anotherEventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(anotherCaseInstance.getId()).singleResult(); + EventSubscription anotherEventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(anotherCaseInstance.getId()).singleResult(); assertThat(anotherEventSubscription.getEventName()).isEqualTo("testSignal"); Task task2 = processEngineTaskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); @@ -259,8 +247,7 @@ public void testTerminateCaseInstanceWithSignal() { assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); - EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance.getId()).singleResult(); - assertThat(eventSubscription.getEventName()).isEqualTo("testSignal"); + assertThatEventSubscriptionExists(caseInstance); cmmnRuntimeService.terminateCaseInstance(caseInstance.getId()); @@ -301,4 +288,45 @@ public void testPassVariablesThroughCaseInstanceService() { } + @Test + @CmmnDeployment(resources = { "org/flowable/cmmn/test/processTaskWithSignalListener.cmmn" }) + public void multipleSignalSubscribers() { + Deployment deployment = this.processEngineRepositoryService.createDeployment(). + addClasspathResource("org/flowable/cmmn/test/signalProcess.bpmn20.xml"). + deploy(); + + try { + CaseInstance caseInstance1 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("signalCase").start(); + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("signalCase").start(); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance1.getId()).count()).isZero(); + assertThat(cmmnRuntimeService.createEventSubscriptionQuery().count()).isEqualTo(2L); + + Task task = processEngineTaskService.createTaskQuery().caseInstanceIdWithChildren(caseInstance1.getId()).singleResult(); + + assertThatCaseTaskWasCreated(task); + assertThatEventSubscriptionExists(caseInstance1); + assertThatEventSubscriptionExists(caseInstance2); + + processEngineTaskService.complete(task.getId()); + + assertThat(cmmnRuntimeService.createEventSubscriptionQuery().count()) + .as("The signal triggers 2 subscribers.") + .isZero(); + } finally { + processEngine.getRepositoryService().deleteDeployment(deployment.getId(), true); + } + } + + private void assertThatEventSubscriptionExists(CaseInstance caseInstance1) { + EventSubscription eventSubscription = cmmnRuntimeService.createEventSubscriptionQuery().scopeId(caseInstance1.getId()).singleResult(); + assertThat(eventSubscription.getEventName()).isEqualTo("testSignal"); + } + + private static void assertThatCaseTaskWasCreated(Task task) { + assertThat(task).isNotNull(); + assertThat(task.getTaskDefinitionKey()).isEqualTo("theTask"); + assertThat(task.getName()).isEqualTo("my task"); + } + } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/SignalEventHandler.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/SignalEventHandler.java index 0555264646c..fb73068a33b 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/SignalEventHandler.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/SignalEventHandler.java @@ -24,13 +24,14 @@ import org.flowable.engine.impl.util.ProcessInstanceHelper; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.eventsubscription.service.impl.persistence.entity.EventSubscriptionEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Daniel Meyer * @author Joram Barrez */ public class SignalEventHandler extends AbstractEventHandler { - public static final String EVENT_HANDLER_TYPE = "signal"; @Override @@ -51,10 +52,6 @@ public void handleEvent(EventSubscriptionEntity eventSubscription, Object payloa org.flowable.bpmn.model.Process process = ProcessDefinitionUtil.getProcess(processDefinitionId); ProcessDefinition processDefinition = ProcessDefinitionUtil.getProcessDefinition(processDefinitionId); - if (processDefinition.isSuspended()) { - throw new FlowableException("Could not handle signal: process definition with id: " + processDefinitionId + " is suspended for " + eventSubscription); - } - // Start process instance via the flow element linked to the event FlowElement flowElement = process.getFlowElement(eventSubscription.getActivityId(), true); if (flowElement == null) { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/signal/SignalEventTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/signal/SignalEventTest.java index f00977674c8..d0da2fdcffd 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/signal/SignalEventTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/signal/SignalEventTest.java @@ -537,6 +537,25 @@ public void testSignalUserTask() { .isExactlyInstanceOf(FlowableException.class); } + @Test + @Deployment(resources={"org/flowable/engine/test/bpmn/event/signal/SignalEventTest.testSignalStartEvent.bpmn20.xml"}) + public void testDuplicatedSuspendedSignalStartEventFromProcess() { + repositoryService.suspendProcessDefinitionByKey("processWithSignalStart1"); + + // An example of behavior when there is no subscription to the signal. + runtimeService.signalEventReceived("nonExisting"); + + // Starting the process that fires the signal should start 2 process + // instances that are listening on that signal. The suspended one is not included. + runtimeService.startProcessInstanceByKey("processWithSignalThrow"); + + // Verify + assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(2); + assertThat(taskService.createTaskQuery().list()).extracting(Task::getName) + .containsExactlyInAnyOrder("Task in process B", "Task in process C"); + + } + @Test public void testSignalStartEventFromProcess() { @@ -805,12 +824,13 @@ public void testSignalStartEventWithSuspendedDefinition() { repositoryService.suspendProcessDefinitionByKey("processWithSignalStart1"); - assertThatThrownBy(() -> runtimeService.startProcessInstanceByKey("processWithSignalThrow")) - .as("Suspended process definition should fail") - .isExactlyInstanceOf(FlowableException.class); + runtimeService.startProcessInstanceByKey("processWithSignalThrow"); // Verify - assertThat(runtimeService.createProcessInstanceQuery().count()).isZero(); + assertThat(runtimeService.createProcessInstanceQuery().list()) + .as("Signal throw event is swallowed for suspended process") + .extracting(ProcessInstance::getProcessDefinitionKey) + .containsExactlyInAnyOrder("processWithSignalStart2","processWithSignalStart3"); repositoryService.activateProcessDefinitionByKey("processWithSignalStart1"); @@ -819,8 +839,8 @@ public void testSignalStartEventWithSuspendedDefinition() { runtimeService.startProcessInstanceByKey("processWithSignalThrow"); // Verify - assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(3); - assertThat(taskService.createTaskQuery().count()).isEqualTo(3); + assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(5); + assertThat(taskService.createTaskQuery().count()).isEqualTo(5); // Cleanup cleanup(); diff --git a/modules/flowable-eventsubscription-service/src/main/resources/org/flowable/eventsubscription/service/db/mapping/entity/EventSubscription.xml b/modules/flowable-eventsubscription-service/src/main/resources/org/flowable/eventsubscription/service/db/mapping/entity/EventSubscription.xml index dec505c84ba..cfddaa16945 100644 --- a/modules/flowable-eventsubscription-service/src/main/resources/org/flowable/eventsubscription/service/db/mapping/entity/EventSubscription.xml +++ b/modules/flowable-eventsubscription-service/src/main/resources/org/flowable/eventsubscription/service/db/mapping/entity/EventSubscription.xml @@ -243,9 +243,11 @@ where EVENT_TYPE_ = 'signal' and EVENT_NAME_ = #{parameter.eventName, jdbcType=NVARCHAR} and ( - (EVT.EXECUTION_ID_ is null) - or - (EVT.EXECUTION_ID_ is not null AND EXC.SUSPENSION_STATE_ = 1) + (EVT.EXECUTION_ID_ is null and EVT.PROC_DEF_ID_ is not null and exists (select 1 from ACT_RE_PROCDEF DEF where DEF.ID_ = EVT.PROC_DEF_ID_ and DEF.SUSPENSION_STATE_ = 1)) + or + (EVT.EXECUTION_ID_ is not null AND EXC.SUSPENSION_STATE_ = 1) + or + (EVT.SCOPE_TYPE_ is not null and EVT.SCOPE_ID_ is not null and EVT.SCOPE_DEFINITION_ID_ is not null and EVT.SUB_SCOPE_ID_ is not null) ) and EVT.TENANT_ID_ = #{parameter.tenantId, jdbcType=NVARCHAR}