Skip to content

Commit

Permalink
Fix issue with stage plan item instance selection in case migration +…
Browse files Browse the repository at this point in the history
… terminate plan item instances that are not present in cmmn model automatically
  • Loading branch information
tijsrademakers committed Sep 10, 2023
1 parent f8ca582 commit 897ddc5
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.flowable.cmmn.engine.impl.persistence.entity;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
Expand All @@ -29,10 +30,12 @@
import org.flowable.cmmn.converter.util.PlanItemDependencyUtil;
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
import org.flowable.cmmn.engine.impl.persistence.entity.data.PlanItemInstanceDataManager;
import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil;
import org.flowable.cmmn.engine.impl.runtime.PlanItemInstanceQueryImpl;
import org.flowable.cmmn.engine.impl.util.CaseInstanceUtil;
import org.flowable.cmmn.engine.impl.util.CommandContextUtil;
import org.flowable.cmmn.engine.impl.util.ExpressionUtil;
import org.flowable.cmmn.model.CmmnModel;
import org.flowable.cmmn.model.EventListener;
import org.flowable.cmmn.model.PlanFragment;
import org.flowable.cmmn.model.PlanItem;
Expand Down Expand Up @@ -480,8 +483,20 @@ public void updatePlanItemInstancesCaseDefinitionId(String caseInstanceId, Strin
.caseInstanceId(caseInstanceId)
.includeEnded();
List<PlanItemInstance> planItemInstances = findByCriteria(planItemQuery);
if (planItemInstances != null) {
if (planItemInstances != null && !planItemInstances.isEmpty()) {
List<String> endStates = Arrays.asList(PlanItemInstanceState.UNAVAILABLE, PlanItemInstanceState.DISABLED, PlanItemInstanceState.COMPLETED, PlanItemInstanceState.TERMINATED, PlanItemInstanceState.FAILED);
CmmnModel cmmnModel = CaseDefinitionUtil.getCmmnModel(caseDefinitionId);
for (PlanItemInstance planItemInstance : planItemInstances) {
if (!endStates.contains(planItemInstance.getState())) {
if (cmmnModel.findPlanItemByPlanItemDefinitionId(planItemInstance.getPlanItemDefinitionId()) == null) {
PlanItemInstanceEntity planItemInstanceEntity = (PlanItemInstanceEntity) planItemInstance;
planItemInstanceEntity.setState(PlanItemInstanceState.TERMINATED);
planItemInstanceEntity.setEndedTime(engineConfiguration.getClock().getCurrentTime());
planItemInstanceEntity.setTerminatedTime(planItemInstanceEntity.getEndedTime());
CommandContextUtil.getCmmnHistoryManager(commandContext).recordPlanItemInstanceTerminated(planItemInstanceEntity);
}
}

PlanItemInstanceEntity planItemInstanceEntity = (PlanItemInstanceEntity) planItemInstance;
planItemInstanceEntity.setCaseDefinitionId(caseDefinitionId);
update(planItemInstanceEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,19 @@ public void setCurrentPlanItemInstances(Map<String, List<PlanItemInstanceEntity>
}

public PlanItemInstanceEntity getRuntimePlanItemInstance(String planItemDefinitionId) {
PlanItemInstanceEntity foundPlanItemInstance = null;
if (currentPlanItemInstances != null && currentPlanItemInstances.containsKey(planItemDefinitionId)) {
List<PlanItemInstanceEntity> currentPlanItemInstanceList = currentPlanItemInstances.get(planItemDefinitionId);
for (PlanItemInstanceEntity planItemInstance : currentPlanItemInstanceList) {
if (!PlanItemInstanceState.TERMINAL_STATES.contains(planItemInstance.getState())) {
return planItemInstance;
if (!PlanItemInstanceState.TERMINAL_STATES.contains(planItemInstance.getState()) &&
(foundPlanItemInstance == null || !PlanItemInstanceState.WAITING_FOR_REPETITION.equals(planItemInstance.getState()))) {

foundPlanItemInstance = planItemInstance;
}
}
}

return null;
return foundPlanItemInstance;
}

public Map<String, List<PlanItemInstanceEntity>> getActivePlanItemInstances() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,73 @@ void withChangingTheAssignee() {
}
}

@Test
void withNoNewPlanItemForPlanItemInstance() {
// Arrange
deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml");
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start();
CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/one-task.cmmn.xml");

// Act
cmmnMigrationService.createCaseInstanceMigrationBuilder()
.migrateToCaseDefinition(destinationDefinition.getId())
.migrate(caseInstance.getId());

// Assert
CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery()
.caseInstanceId(caseInstance.getId())
.singleResult();
assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId());
List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery()
.caseInstanceId(caseInstance.getId())
.includeEnded()
.list();
assertThat(planItemInstances).hasSize(2);
assertThat(planItemInstances)
.extracting(PlanItemInstance::getCaseDefinitionId)
.containsOnly(destinationDefinition.getId());
assertThat(planItemInstances)
.extracting(PlanItemInstance::getName)
.containsExactlyInAnyOrder("Task 1", "Task 2");
assertThat(planItemInstances)
.filteredOn("name", "Task 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.ACTIVE);

assertThat(planItemInstances)
.filteredOn("name", "Task 2")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.TERMINATED);

Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("humanTask1").singleResult();
cmmnTaskService.complete(task.getId());

assertThat(cmmnRuntimeService.createCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isZero();

if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) {
assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1);
assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId())
.isEqualTo(destinationDefinition.getId());

List<HistoricPlanItemInstance> historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery()
.planItemInstanceCaseInstanceId(caseInstance.getId()).list();
assertThat(historicPlanItemInstances).hasSize(2);
for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) {
assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId());
}

assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().planItemInstanceCaseInstanceId(caseInstance.getId()).planItemInstanceDefinitionId("humanTask1").singleResult().getState()).isEqualTo(PlanItemInstanceState.COMPLETED);
assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().planItemInstanceCaseInstanceId(caseInstance.getId()).planItemInstanceDefinitionId("humanTask2").singleResult().getState()).isEqualTo(PlanItemInstanceState.TERMINATED);

List<HistoricTaskInstance> historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list();
assertThat(historicTasks).hasSize(2);
for (HistoricTaskInstance historicTask : historicTasks) {
assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId());
assertThat(historicTask.getEndTime()).isNotNull();
}
}
}

@Test
void withChangingPlanItemId() {
// Arrange
Expand Down Expand Up @@ -1345,6 +1412,162 @@ void stageWithListener() {
"humanTask4", "userEventListener1", "userEventListener2");
}

@Test
void withActivateStage() {
// Arrange
deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/case-with-stage.cmmn.xml");
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("migrationCase").start();
CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/case-with-stage.cmmn.xml");

// Act
cmmnMigrationService.createCaseInstanceMigrationBuilder()
.migrateToCaseDefinition(destinationDefinition.getId())
.addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("cmmnStage1"))
.migrate(caseInstance.getId());

// Assert
CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery()
.caseInstanceId(caseInstance.getId())
.singleResult();
assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId());
List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery()
.caseInstanceId(caseInstance.getId())
.includeEnded()
.list();
assertThat(planItemInstances).hasSize(3);
assertThat(planItemInstances)
.extracting(PlanItemInstance::getCaseDefinitionId)
.containsOnly(destinationDefinition.getId());
assertThat(planItemInstances)
.extracting(PlanItemInstance::getName)
.containsExactlyInAnyOrder("Stage 1", "Stage 1", "Human task 1");
assertThat(planItemInstances)
.filteredOn("name", "Human task 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.ACTIVE);

assertThat(planItemInstances)
.filteredOn("name", "Stage 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.WAITING_FOR_REPETITION, PlanItemInstanceState.ACTIVE);

PlanItemInstance stagePlanItem = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).planItemDefinitionId("cmmnStage1").planItemInstanceState(PlanItemInstanceState.ACTIVE).singleResult();

PlanItemInstance taskPlanItem = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).planItemDefinitionId("stageTask1").singleResult();

assertThat(taskPlanItem.getStageInstanceId()).isEqualTo(stagePlanItem.getId());

Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
cmmnTaskService.complete(task.getId());

assertThat(cmmnRuntimeService.createCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isZero();

if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) {
assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1);
assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId())
.isEqualTo(destinationDefinition.getId());

List<HistoricPlanItemInstance> historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery()
.planItemInstanceCaseInstanceId(caseInstance.getId()).list();
assertThat(historicPlanItemInstances).hasSize(3);
for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) {
assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId());
}

List<HistoricTaskInstance> historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list();
assertThat(historicTasks).hasSize(1);
for (HistoricTaskInstance historicTask : historicTasks) {
assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId());
}
}
}

@Test
void withActivateAnotherRepetitionStage() {
// Arrange
deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/case-with-active-stage.cmmn.xml");
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("migrationCase").start();
CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/case-with-active-stage.cmmn.xml");

List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery()
.caseInstanceId(caseInstance.getId())
.includeEnded()
.list();
assertThat(planItemInstances).hasSize(3);

assertThat(planItemInstances)
.extracting(PlanItemInstance::getName)
.containsExactlyInAnyOrder("Stage 1", "Stage 1", "Human task 1");
assertThat(planItemInstances)
.filteredOn("name", "Human task 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.ACTIVE);

assertThat(planItemInstances)
.filteredOn("name", "Stage 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.WAITING_FOR_REPETITION, PlanItemInstanceState.ACTIVE);

// Act
cmmnMigrationService.createCaseInstanceMigrationBuilder()
.migrateToCaseDefinition(destinationDefinition.getId())
.addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("stageTask1"))
.migrate(caseInstance.getId());

// Assert
CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery()
.caseInstanceId(caseInstance.getId())
.singleResult();
assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId());
planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery()
.caseInstanceId(caseInstance.getId())
.includeEnded()
.list();
assertThat(planItemInstances).hasSize(4);
assertThat(planItemInstances)
.extracting(PlanItemInstance::getCaseDefinitionId)
.containsOnly(destinationDefinition.getId());
assertThat(planItemInstances)
.extracting(PlanItemInstance::getName)
.containsExactlyInAnyOrder("Stage 1", "Stage 1", "Human task 1", "Human task 1");
assertThat(planItemInstances)
.filteredOn("name", "Human task 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.ACTIVE, PlanItemInstanceState.ACTIVE);

assertThat(planItemInstances)
.filteredOn("name", "Stage 1")
.extracting(PlanItemInstance::getState)
.containsOnly(PlanItemInstanceState.WAITING_FOR_REPETITION, PlanItemInstanceState.ACTIVE);

PlanItemInstance stagePlanItem = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).planItemDefinitionId("cmmnStage1").planItemInstanceState(PlanItemInstanceState.ACTIVE).singleResult();

List<PlanItemInstance> taskPlanItems = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).planItemDefinitionId("stageTask1").list();

for (PlanItemInstance taskPlanItemInstance : taskPlanItems) {
assertThat(taskPlanItemInstance.getStageInstanceId()).isEqualTo(stagePlanItem.getId());
}

if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) {
assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1);
assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId())
.isEqualTo(destinationDefinition.getId());

List<HistoricPlanItemInstance> historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery()
.planItemInstanceCaseInstanceId(caseInstance.getId()).list();
assertThat(historicPlanItemInstances).hasSize(4);
for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) {
assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId());
}

List<HistoricTaskInstance> historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list();
assertThat(historicTasks).hasSize(2);
for (HistoricTaskInstance historicTask : historicTasks) {
assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId());
}
}
}

@Test
void terminateListener() {
// Arrange
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" xmlns:design="http://flowable.org/design" targetNamespace="http://flowable.org/cmmn" design:palette="flowable-core-case-palette">
<case id="migrationCase" name="Migration Case">
<casePlanModel id="onecaseplanmodel1" name="Case plan model">
<planItem id="planItemcmmnStage1" definitionRef="cmmnStage1" name="Stage 1">
<itemControl>
<repetitionRule flowable:counterVariable="repetitionCounter" flowable:maxInstanceCount="1" />
</itemControl>
<entryCriterion id="cmmnEntrySentry" sentryRef="sentrycmmnEntrySentry"></entryCriterion>
</planItem>
<sentry id="sentrycmmnEntrySentry" flowable:triggerMode="onEvent">
<ifPart id="sentryIfPart_cmmnEntrySentry">
<condition><![CDATA[${true}]]></condition>
</ifPart>
</sentry>
<stage id="cmmnStage1" name="Stage 1">
<planItem id="planItemcmmnTask_1" name="Human task 1" definitionRef="stageTask1"></planItem>
<humanTask id="stageTask1" name="Human task 1" />
</stage>
</casePlanModel>
</case>
</definitions>
Loading

0 comments on commit 897ddc5

Please sign in to comment.