Skip to content

Commit f1c0d4d

Browse files
committed
Fix process and case task plan item instance not getting the correct state on case instance deletion
1 parent a74447c commit f1c0d4d

4 files changed

Lines changed: 102 additions & 2 deletions

File tree

modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,4 +2288,56 @@ public void testInMappingArrayNodeWithSyncFirstStep() {
22882288
}
22892289
}
22902290

2291+
@Test
2292+
@CmmnDeployment
2293+
@org.flowable.engine.test.Deployment(resources = "org/flowable/cmmn/test/oneTaskProcess.bpmn20.xml")
2294+
public void testTerminateCaseInstanceWithBlockingProcessTaskHistoricPlanItemState() {
2295+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("myCase").start();
2296+
2297+
// Complete the human task so the process task becomes active
2298+
Task caseTask = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
2299+
assertThat(caseTask).isNotNull();
2300+
assertThat(caseTask.getName()).isEqualTo("Task A");
2301+
cmmnTaskService.complete(caseTask.getId());
2302+
2303+
// Process task should now be active with a child process instance
2304+
PlanItemInstance processTaskPlanItem = cmmnRuntimeService.createPlanItemInstanceQuery()
2305+
.caseInstanceId(caseInstance.getId())
2306+
.planItemInstanceState(PlanItemInstanceState.ACTIVE)
2307+
.planItemDefinitionType(PlanItemDefinitionType.PROCESS_TASK)
2308+
.singleResult();
2309+
assertThat(processTaskPlanItem).isNotNull();
2310+
2311+
// The child process instance should have a user task
2312+
Task processTask = processEngine.getTaskService().createTaskQuery().singleResult();
2313+
assertThat(processTask).isNotNull();
2314+
assertThat(processTask.getName()).isEqualTo("my task");
2315+
2316+
// Terminate the case instance
2317+
cmmnRuntimeService.terminateCaseInstance(caseInstance.getId());
2318+
2319+
// Verify runtime data is cleaned up
2320+
assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isZero();
2321+
assertThat(processEngine.getTaskService().createTaskQuery().count()).isZero();
2322+
assertThat(processEngineRuntimeService.createProcessInstanceQuery().count()).isZero();
2323+
2324+
if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) {
2325+
// The historic case instance should be terminated
2326+
HistoricCaseInstance historicCaseInstance = cmmnHistoryService.createHistoricCaseInstanceQuery()
2327+
.caseInstanceId(caseInstance.getId())
2328+
.singleResult();
2329+
assertThat(historicCaseInstance).isNotNull();
2330+
assertThat(historicCaseInstance.getState()).isEqualTo(CaseInstanceState.TERMINATED);
2331+
2332+
// The historic plan item for the process task should be terminated, not active
2333+
HistoricPlanItemInstance historicProcessTaskPlanItem = cmmnHistoryService.createHistoricPlanItemInstanceQuery()
2334+
.planItemInstanceId(processTaskPlanItem.getId())
2335+
.singleResult();
2336+
assertThat(historicProcessTaskPlanItem).isNotNull();
2337+
assertThat(historicProcessTaskPlanItem.getState()).isEqualTo(PlanItemInstanceState.TERMINATED);
2338+
assertThat(historicProcessTaskPlanItem.getEndedTime()).isNotNull();
2339+
assertThat(historicProcessTaskPlanItem.getTerminatedTime()).isNotNull();
2340+
}
2341+
}
2342+
22912343
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI"
3+
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="http://flowable.org/cmmn">
4+
5+
<case id="myCase">
6+
<casePlanModel id="myPlanModel" name="My CasePlanModel">
7+
8+
<planItem id="planItem1" name="Task A" definitionRef="humanTask" />
9+
<planItem id="planItem2" name="The Process" definitionRef="theProcess">
10+
<entryCriterion sentryRef="sentry1" />
11+
</planItem>
12+
13+
<sentry id="sentry1">
14+
<planItemOnPart sourceRef="planItem1">
15+
<standardEvent>complete</standardEvent>
16+
</planItemOnPart>
17+
</sentry>
18+
19+
<humanTask id="humanTask" name="Task A" />
20+
<processTask id="theProcess" processRef="oneTaskProcess" isBlocking="true" />
21+
22+
</casePlanModel>
23+
</case>
24+
25+
<process id="oneTaskProcess" name="The One Task process" externalRef="oneTask" />
26+
27+
</definitions>

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,17 @@ public void deleteChildEntity(CommandContext commandContext, DelegatePlanItemIns
230230
caseInstance, CaseInstanceState.TERMINATED, cmmnEngineConfiguration.getClock().getCurrentTime());
231231
caseInstanceEntityManager.delete(caseInstance.getId(), cascade, null);
232232
}
233-
233+
234+
// This is not the regular termination through the agenda, but the historic plan item state still needs to be correct.
235+
// The child case instance needs to be deleted synchronously (not deferred via the agenda) to preserve entity link cleanup ordering.
236+
PlanItemInstanceEntity planItemInstanceEntity = (PlanItemInstanceEntity) delegatePlanItemInstance;
237+
if (!PlanItemInstanceState.TERMINATED.equals(planItemInstanceEntity.getState())) {
238+
planItemInstanceEntity.setState(PlanItemInstanceState.TERMINATED);
239+
planItemInstanceEntity.setEndedTime(CommandContextUtil.getCmmnEngineConfiguration(commandContext).getClock().getCurrentTime());
240+
planItemInstanceEntity.setTerminatedTime(planItemInstanceEntity.getEndedTime());
241+
CommandContextUtil.getCmmnHistoryManager(commandContext).recordPlanItemInstanceTerminated(planItemInstanceEntity);
242+
}
243+
234244
} else {
235245
throw new FlowableException("Can only delete a child entity for a plan item with reference type " + ReferenceTypes.PLAN_ITEM_CHILD_CASE + " for " + delegatePlanItemInstance);
236246
}

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,18 @@ protected void deleteProcessInstance(CommandContext commandContext, DelegatePlan
179179
@Override
180180
public void deleteChildEntity(CommandContext commandContext, DelegatePlanItemInstance delegatePlanItemInstance, boolean cascade) {
181181
if (ReferenceTypes.PLAN_ITEM_CHILD_PROCESS.equals(delegatePlanItemInstance.getReferenceType())) {
182-
delegatePlanItemInstance.setState(PlanItemInstanceState.TERMINATED); // This is not the regular termination, but the state still needs to be correct
182+
// This is not the regular termination through the agenda, but the historic plan item state still needs to be correct.
183+
// The state needs to be set before deleting the process instance, because the process instance deletion triggers
184+
// a ChildProcessInstanceStateChangeCallback which re-enters the CMMN engine to terminate the plan item.
185+
// With the state already set to TERMINATED, this callback becomes a no-op and avoids re-triggering repetition rules.
186+
PlanItemInstanceEntity planItemInstanceEntity = (PlanItemInstanceEntity) delegatePlanItemInstance;
187+
if (!PlanItemInstanceState.TERMINATED.equals(planItemInstanceEntity.getState())) {
188+
planItemInstanceEntity.setState(PlanItemInstanceState.TERMINATED);
189+
planItemInstanceEntity.setEndedTime(CommandContextUtil.getCmmnEngineConfiguration(commandContext).getClock().getCurrentTime());
190+
planItemInstanceEntity.setTerminatedTime(planItemInstanceEntity.getEndedTime());
191+
CommandContextUtil.getCmmnHistoryManager(commandContext).recordPlanItemInstanceTerminated(planItemInstanceEntity);
192+
}
193+
183194
deleteProcessInstance(commandContext, delegatePlanItemInstance);
184195
} else {
185196
throw new FlowableException("Can only delete a child entity for a plan item with reference type " + ReferenceTypes.PLAN_ITEM_CHILD_PROCESS + " for " + delegatePlanItemInstance);

0 commit comments

Comments
 (0)