Skip to content

Commit

Permalink
Fix class cast exception when using a multi instance case task in a p…
Browse files Browse the repository at this point in the history
…rocess
  • Loading branch information
filiphr committed Apr 21, 2023
1 parent 6e0cf1c commit d002b0b
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public void handleSignalEvent(EventSubscriptionEntity eventSubscription, Map<Str
public void deleteCaseInstance(String caseInstanceId) {
cmmnEngineConfiguration.getCommandExecutor().execute(commandContext -> {
CaseInstanceEntity caseInstanceEntity = CommandContextUtil.getCaseInstanceEntityManager(commandContext).findById(caseInstanceId);
if (caseInstanceEntity == null || caseInstanceEntity.isDeleted()) {
if (caseInstanceEntity == null || caseInstanceEntity.isDeleted() || CaseInstanceState.isInTerminalState(caseInstanceEntity)) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<String, Object> 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<CaseInstance> caseInstances = cmmnRuntimeService.createCaseInstanceQuery().list();
CaseInstance caseInstance = caseInstances.get(0);
Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
Map<String, Object> 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<Void> {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/bpmn"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL https://www.omg.org/spec/BPMN/20100501/BPMN20.xsd"
targetNamespace="http://flowable.org/bpmn">
<process id="caseTask">
<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask1"/>
<serviceTask flowable:type="case" id="theTask1" flowable:caseDefinitionKey="myCase">
<extensionElements>
<flowable:out source="approved" target="approved"/>
<flowable:out source="description" target="description"/>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="false">
<extensionElements>
<flowable:variableAggregation target="reviews" createOverviewVariable="true">
<variable source="approved"/>
<variable source="description"/>
</flowable:variableAggregation>
</extensionElements>
<loopCardinality>${nrOfLoops}</loopCardinality>
</multiInstanceLoopCharacteristics>
</serviceTask>
<sequenceFlow id="flow2" sourceRef="theTask1" targetRef="theTask2"/>
<userTask id="theTask2" name="my task"/>
<sequenceFlow id="flow3" sourceRef="theTask2" targetRef="theEnd"/>
<endEvent id="theEnd"/>


</process>
</definitions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/bpmn"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL https://www.omg.org/spec/BPMN/20100501/BPMN20.xsd"
targetNamespace="http://flowable.org/bpmn">
<process id="caseTask">
<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask1"/>
<serviceTask flowable:type="case" id="theTask1" flowable:caseDefinitionKey="myCase">
<extensionElements>
<flowable:out source="approved" target="approved"/>
<flowable:out source="description" target="description"/>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="true">
<extensionElements>
<flowable:variableAggregation target="reviews" createOverviewVariable="true">
<variable source="approved"/>
<variable source="description"/>
</flowable:variableAggregation>
</extensionElements>
<loopCardinality>${nrOfLoops}</loopCardinality>
</multiInstanceLoopCharacteristics>
</serviceTask>
<sequenceFlow id="flow2" sourceRef="theTask1" targetRef="theTask2"/>
<userTask id="theTask2" name="my task"/>
<sequenceFlow id="flow3" sourceRef="theTask2" targetRef="theEnd"/>
<endEvent id="theEnd"/>


</process>
</definitions>
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ public void completed(DelegateExecution execution) throws Exception {
// not used
}

public void triggerCaseTaskAndLeave(DelegateExecution execution, Map<String, Object> variables) {
triggerCaseTask(execution, variables);
leave(execution);
}

public void triggerCaseTask(DelegateExecution execution, Map<String, Object> variables) {
execution.setVariables(variables);
ExecutionEntity executionEntity = (ExecutionEntity) execution;
Expand All @@ -176,7 +181,5 @@ public void triggerCaseTask(DelegateExecution execution, Map<String, Object> var
// Set the reference id and type to null since the execution could be reused
executionEntity.setReferenceId(null);
executionEntity.setReferenceType(null);

leave(execution);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

0 comments on commit d002b0b

Please sign in to comment.