diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/CaseInstanceUtil.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/CaseInstanceUtil.java index 5a6a2ee6d22..cc7bdfe95a9 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/CaseInstanceUtil.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/CaseInstanceUtil.java @@ -74,7 +74,7 @@ public static Map> findChildPlanItemInstanc for (PlanItemInstanceEntity childPlanItemInstance : childPlanItemInstances) { PlanItem childPlanItem = childPlanItemInstance.getPlanItem(); - if (planItemIds.contains(childPlanItem.getId())) { + if (childPlanItem != null && planItemIds.contains(childPlanItem.getId())) { if (!result.containsKey(childPlanItem.getId())) { result.put(childPlanItem.getId(), new ArrayList<>()); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/ExpressionUtil.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/ExpressionUtil.java index 56a20667a62..11b6be85285 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/ExpressionUtil.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/ExpressionUtil.java @@ -212,6 +212,8 @@ public static List searchNonFinishedEqualPlanItemInstances(Pla if (planItemInstanceContainer != null && planItemInstanceContainer.getChildPlanItemInstances() != null) { return planItemInstanceContainer.getChildPlanItemInstances() .stream() + .filter(pi -> planItemInstanceEntity.getPlanItem() != null) + .filter(pi -> pi.getPlanItem() != null) .filter(pi -> planItemInstanceEntity.getPlanItem().getId().equals(pi.getPlanItem().getId())) .filter(pi -> !PlanItemInstanceState.isInTerminalState(pi)) .filter(pi -> !pi.getId().equals(planItemInstanceEntity.getId())) diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java index 2b736b344ec..f652d4724d1 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java @@ -36,6 +36,7 @@ import org.flowable.cmmn.api.runtime.MilestoneInstance; import org.flowable.cmmn.api.runtime.PlanItemInstance; import org.flowable.cmmn.api.runtime.PlanItemInstanceState; +import org.flowable.cmmn.api.runtime.UserEventListenerInstance; import org.flowable.cmmn.engine.impl.persistence.entity.SentryPartInstanceEntity; import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; import org.flowable.common.engine.api.FlowableException; @@ -1881,6 +1882,60 @@ void listenerAndTaskInStagesWithSentryAndIfPart() { assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).planItemDefinitionId("expandedStage2").singleResult().getState()).isEqualTo(PlanItemInstanceState.ACTIVE); } + @Test + void repetitionListenerAndChangedTask() { + // Arrange + CaseDefinition originalDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/repetition-with-listener-and-task.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("repetitionTaskCase") + .variable("exitVar", "test") + .start(); + + List planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(planItemInstances).hasSize(4); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getCaseDefinitionId) + .containsOnly(originalDefinition.getId()); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getPlanItemDefinitionId) + .containsExactlyInAnyOrder("userEventListener1", "repeatableTask", "cmmnStage1", "stageTask"); + + UserEventListenerInstance userEventListenerInstance = cmmnRuntimeService.createUserEventListenerInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnRuntimeService.completeUserEventListenerInstance(userEventListenerInstance.getId()); + + planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(planItemInstances).hasSize(4); + PlanItemInstance planItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).planItemDefinitionId("repeatableTask").singleResult(); + assertThat(planItemInstance.getState()).isEqualTo(PlanItemInstanceState.WAITING_FOR_REPETITION); + + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/repetition-with-listener-and-changed-task.cmmn.xml"); + + // Act + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .removeWaitingForRepetitionPlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createRemoveWaitingForRepetitionPlanItemDefinitionMappingFor("repeatableTask")) + .addMoveToAvailablePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createMoveToAvailablePlanItemDefinitionMappingFor("changedTask")) + .addTerminatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createTerminatePlanItemDefinitionMappingFor("stageTask")) + .migrate(caseInstance.getId()); + + // Assert + CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(planItemInstances).hasSize(2); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getCaseDefinitionId) + .containsOnly(destinationDefinition.getId()); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getPlanItemDefinitionId) + .containsExactlyInAnyOrder("userEventListener1", "changedTask"); + + userEventListenerInstance = cmmnRuntimeService.createUserEventListenerInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnRuntimeService.completeUserEventListenerInstance(userEventListenerInstance.getId()); + } + @Test void activateNewStageWithSentry() { // Arrange diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/repetition-with-listener-and-changed-task.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/repetition-with-listener-and-changed-task.cmmn.xml new file mode 100644 index 00000000000..c30a9a2747a --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/repetition-with-listener-and-changed-task.cmmn.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + occur + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/repetition-with-listener-and-task.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/repetition-with-listener-and-task.cmmn.xml new file mode 100644 index 00000000000..5f7b2171c1d --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/repetition-with-listener-and-task.cmmn.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + occur + + + + + + + + + + + + + + + + \ No newline at end of file