diff --git a/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java b/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java index 075d1fddbac..793ef808eb3 100644 --- a/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java +++ b/modules/flowable-cmmn-engine-configurator/src/main/java/org/flowable/cmmn/engine/configurator/impl/cmmn/DefaultCaseInstanceService.java @@ -119,7 +119,7 @@ public void handleSignalEvent(EventSubscriptionEntity eventSubscription, Map { CaseInstanceEntity caseInstanceEntity = CommandContextUtil.getCaseInstanceEntityManager(commandContext).findById(caseInstanceId); - if (caseInstanceEntity == null || caseInstanceEntity.isDeleted()) { + if (caseInstanceEntity == null || caseInstanceEntity.isDeleted() || CaseInstanceState.isInTerminalState(caseInstanceEntity)) { return null; } diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java index 62567075d10..90b0db1649d 100644 --- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java +++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/CaseTaskTest.java @@ -12,6 +12,7 @@ */ package org.flowable.cmmn.test; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; @@ -30,12 +31,14 @@ import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.constant.ReferenceTypes; import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.impl.history.HistoryLevel; import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.impl.ExecutionQueryImpl; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.test.HistoryTestHelper; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.repository.Deployment; import org.flowable.engine.runtime.Execution; @@ -46,8 +49,12 @@ import org.flowable.entitylink.api.history.HistoricEntityLink; import org.flowable.task.api.Task; import org.flowable.variable.api.history.HistoricVariableInstance; +import org.flowable.variable.api.persistence.entity.VariableInstance; +import org.flowable.variable.service.impl.types.JsonType; import org.junit.Test; +import net.javacrumbs.jsonunit.core.Option; + /** * @author Tijs Rademakers * @author Joram Barrez @@ -1115,6 +1122,144 @@ public void testCompleteCaseTaskWithSuspendedParentProcessInstance() { } } + @Test + @CmmnDeployment(resources = { "org/flowable/cmmn/test/CaseTaskTest.testCaseTask.cmmn" }) + public void testSequentialMultiInstanceCaseTask() { + Deployment deployment = processEngineRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/caseTaskSequentialMultiInstanceProcess.bpmn20.xml") + .deploy(); + + try { + ProcessInstance processInstance = processEngineRuntimeService.createProcessInstanceBuilder() + .processDefinitionKey("caseTask") + .variable("nrOfLoops", 3) + .start(); + + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceQuery().singleResult(); + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + Map variables = new HashMap<>(); + variables.put("approved", false); + variables.put("description", "description task 0"); + cmmnTaskService.complete(task.getId(), variables); + + caseInstance = cmmnRuntimeService.createCaseInstanceQuery().singleResult(); + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + variables = new HashMap<>(); + variables.put("approved", true); + variables.put("description", "description task 1"); + cmmnTaskService.complete(task.getId(), variables); + + caseInstance = cmmnRuntimeService.createCaseInstanceQuery().singleResult(); + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + variables = new HashMap<>(); + variables.put("approved", false); + variables.put("description", "description task 2"); + cmmnTaskService.complete(task.getId(), variables); + + assertThat(processEngineRuntimeService.getVariable(processInstance.getId(), "approved")).isNull(); + assertThat(processEngineRuntimeService.getVariable(processInstance.getId(), "description")).isNull(); + + VariableInstance reviews = processEngineRuntimeService.getVariableInstance(processInstance.getId(), "reviews"); + + assertThat(reviews).isNotNull(); + assertThat(reviews.getTypeName()).isEqualTo(JsonType.TYPE_NAME); + assertThatJson(reviews.getValue()) + .isEqualTo("[" + + "{ approved: false, description: 'description task 0' }," + + "{ approved: true, description: 'description task 1' }," + + "{ approved: false, description: 'description task 2' }" + + "]"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, (ProcessEngineConfigurationImpl) processEngineConfiguration)) { + HistoricVariableInstance historicReviews = processEngineHistoryService.createHistoricVariableInstanceQuery() + .variableName("reviews") + .singleResult(); + assertThat(historicReviews).isNotNull(); + assertThat(historicReviews.getVariableTypeName()).isEqualTo(JsonType.TYPE_NAME); + assertThatJson(historicReviews.getValue()) + .isEqualTo("[" + + "{ approved: false, description: 'description task 0' }," + + "{ approved: true, description: 'description task 1' }," + + "{ approved: false, description: 'description task 2' }" + + "]"); + } + + } finally { + processEngineRepositoryService.deleteDeployment(deployment.getId(), true); + } + } + + @Test + @CmmnDeployment(resources = { "org/flowable/cmmn/test/CaseTaskTest.testCaseTask.cmmn" }) + public void testParallelMultiInstanceCaseTask() { + Deployment deployment = processEngineRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/caseTaskParallelMultiInstanceProcess.bpmn20.xml") + .deploy(); + + try { + ProcessInstance processInstance = processEngineRuntimeService.createProcessInstanceBuilder() + .processDefinitionKey("caseTask") + .variable("nrOfLoops", 3) + .start(); + + List caseInstances = cmmnRuntimeService.createCaseInstanceQuery().list(); + CaseInstance caseInstance = caseInstances.get(0); + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + Map variables = new HashMap<>(); + variables.put("approved", false); + variables.put("description", "description task 0"); + cmmnTaskService.complete(task.getId(), variables); + + caseInstance = caseInstances.get(1); + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + variables = new HashMap<>(); + variables.put("approved", true); + variables.put("description", "description task 1"); + cmmnTaskService.complete(task.getId(), variables); + + caseInstance = caseInstances.get(2); + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + variables = new HashMap<>(); + variables.put("approved", false); + variables.put("description", "description task 2"); + cmmnTaskService.complete(task.getId(), variables); + + assertThat(processEngineRuntimeService.getVariable(processInstance.getId(), "approved")).isNull(); + assertThat(processEngineRuntimeService.getVariable(processInstance.getId(), "description")).isNull(); + + VariableInstance reviews = processEngineRuntimeService.getVariableInstance(processInstance.getId(), "reviews"); + + assertThat(reviews).isNotNull(); + assertThat(reviews.getTypeName()).isEqualTo(JsonType.TYPE_NAME); + assertThatJson(reviews.getValue()) + .when(Option.IGNORING_ARRAY_ORDER) + .isEqualTo("[" + + "{ approved: false, description: 'description task 0' }," + + "{ approved: true, description: 'description task 1' }," + + "{ approved: false, description: 'description task 2' }" + + "]"); + + if (HistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.AUDIT, (ProcessEngineConfigurationImpl) processEngineConfiguration)) { + HistoricVariableInstance historicReviews = processEngineHistoryService.createHistoricVariableInstanceQuery() + .variableName("reviews") + .singleResult(); + assertThat(historicReviews).isNotNull(); + assertThat(historicReviews.getVariableTypeName()).isEqualTo(JsonType.TYPE_NAME); + assertThatJson(historicReviews.getValue()) + .when(Option.IGNORING_ARRAY_ORDER) + .isEqualTo("[" + + "{ approved: false, description: 'description task 0' }," + + "{ approved: true, description: 'description task 1' }," + + "{ approved: false, description: 'description task 2' }" + + "]"); + } + + } finally { + processEngineRepositoryService.deleteDeployment(deployment.getId(), true); + } + } + + static class ClearExecutionReferenceCmd implements Command { @Override diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskParallelMultiInstanceProcess.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskParallelMultiInstanceProcess.bpmn20.xml new file mode 100644 index 00000000000..2feaa345ce6 --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskParallelMultiInstanceProcess.bpmn20.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + ${nrOfLoops} + + + + + + + + + + diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSequentialMultiInstanceProcess.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSequentialMultiInstanceProcess.bpmn20.xml new file mode 100644 index 00000000000..98127677f70 --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSequentialMultiInstanceProcess.bpmn20.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + ${nrOfLoops} + + + + + + + + + + diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java index d3392d6403d..2f1456462ce 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/CaseTaskActivityBehavior.java @@ -165,6 +165,11 @@ public void completed(DelegateExecution execution) throws Exception { // not used } + public void triggerCaseTaskAndLeave(DelegateExecution execution, Map variables) { + triggerCaseTask(execution, variables); + leave(execution); + } + public void triggerCaseTask(DelegateExecution execution, Map variables) { execution.setVariables(variables); ExecutionEntity executionEntity = (ExecutionEntity) execution; @@ -176,7 +181,5 @@ public void triggerCaseTask(DelegateExecution execution, Map var // Set the reference id and type to null since the execution could be reused executionEntity.setReferenceId(null); executionEntity.setReferenceType(null); - - leave(execution); } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/TriggerCaseTaskCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/TriggerCaseTaskCmd.java index aecc7d50f09..6f3d7ae7500 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/TriggerCaseTaskCmd.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/TriggerCaseTaskCmd.java @@ -21,7 +21,9 @@ import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; import org.flowable.engine.impl.bpmn.behavior.CaseTaskActivityBehavior; +import org.flowable.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.util.CommandContextUtil; @@ -60,10 +62,22 @@ public Void execute(CommandContext commandContext) { } CaseServiceTask caseServiceTask = (CaseServiceTask) flowElement; - - CaseTaskActivityBehavior caseTaskActivityBehavior = (CaseTaskActivityBehavior) caseServiceTask.getBehavior(); - caseTaskActivityBehavior.triggerCaseTask(execution, variables); - + + Object behavior = caseServiceTask.getBehavior(); + if (behavior instanceof CaseTaskActivityBehavior) { + ((CaseTaskActivityBehavior) behavior).triggerCaseTaskAndLeave(execution, variables); + } else if (behavior instanceof MultiInstanceActivityBehavior) { + AbstractBpmnActivityBehavior innerActivityBehavior = ((MultiInstanceActivityBehavior) behavior).getInnerActivityBehavior(); + if (innerActivityBehavior instanceof CaseTaskActivityBehavior) { + ((CaseTaskActivityBehavior) innerActivityBehavior).triggerCaseTask(execution, variables); + } else { + throw new FlowableException("Multi instance inner behavior " + innerActivityBehavior + " is not supported"); + } + ((MultiInstanceActivityBehavior) behavior).leave(execution); + } else { + throw new FlowableException("Behavior " + behavior + " is not supported for a case task"); + } + return null; } }