diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceMigrationCallback.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceMigrationCallback.java index bd6c82c88a2..13f4880a74d 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceMigrationCallback.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceMigrationCallback.java @@ -19,7 +19,17 @@ public interface CaseInstanceMigrationCallback { void caseInstanceMigrated(CaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, CaseInstanceMigrationDocument document); - + + default void caseInstanceMigrated(CaseInstance caseInstance, CaseDefinition sourceCaseDefinition, CaseDefinition caseDefToMigrateTo, + CaseInstanceMigrationDocument document) { + caseInstanceMigrated(caseInstance, caseDefToMigrateTo, document); + } + void historicCaseInstanceMigrated(HistoricCaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, HistoricCaseInstanceMigrationDocument document); - + + default void historicCaseInstanceMigrated(HistoricCaseInstance caseInstance, CaseDefinition sourceCaseDefinition, CaseDefinition caseDefToMigrateTo, + HistoricCaseInstanceMigrationDocument document) { + historicCaseInstanceMigrated(caseInstance, caseDefToMigrateTo, document); + } + } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java index a32367a523c..9881f8345f4 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java @@ -330,8 +330,9 @@ protected void doMigrateCaseInstance(CaseInstanceEntity caseInstance, CaseDefini List migrationCallbacks = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getCaseInstanceMigrationCallbacks(); if (migrationCallbacks != null && !migrationCallbacks.isEmpty()) { + CaseDefinition sourceCaseDefinition = CaseDefinitionUtil.getCaseDefinition(originalCaseDefinitionId); for (CaseInstanceMigrationCallback caseInstanceMigrationCallback : migrationCallbacks) { - caseInstanceMigrationCallback.caseInstanceMigrated(caseInstance, caseDefinitionToMigrateTo, document); + caseInstanceMigrationCallback.caseInstanceMigrated(caseInstance, sourceCaseDefinition, caseDefinitionToMigrateTo, document); } } @@ -359,6 +360,7 @@ protected void doMigrateHistoricCaseInstance(HistoricCaseInstanceEntity historic } LOGGER.debug("Updating case definition reference of case root execution with id:'{}' to '{}'", historicCaseInstance.getId(), caseDefinitionToMigrateTo.getId()); + String originalCaseDefinitionId = historicCaseInstance.getCaseDefinitionId(); historicCaseInstance.setCaseDefinitionId(caseDefinitionToMigrateTo.getId()); historicCaseInstance.setCaseDefinitionKey(caseDefinitionToMigrateTo.getKey()); historicCaseInstance.setCaseDefinitionName(caseDefinitionToMigrateTo.getName()); @@ -371,8 +373,9 @@ protected void doMigrateHistoricCaseInstance(HistoricCaseInstanceEntity historic List migrationCallbacks = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getCaseInstanceMigrationCallbacks(); if (migrationCallbacks != null && !migrationCallbacks.isEmpty()) { + CaseDefinition sourceCaseDefinition = CaseDefinitionUtil.getCaseDefinition(originalCaseDefinitionId); for (CaseInstanceMigrationCallback caseInstanceMigrationCallback : migrationCallbacks) { - caseInstanceMigrationCallback.historicCaseInstanceMigrated(historicCaseInstance, caseDefinitionToMigrateTo, document); + caseInstanceMigrationCallback.historicCaseInstanceMigrated(historicCaseInstance, sourceCaseDefinition, caseDefinitionToMigrateTo, document); } } } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationCallbackTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationCallbackTest.java new file mode 100644 index 00000000000..aa1b99cb2bb --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationCallbackTest.java @@ -0,0 +1,186 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.flowable.cmmn.test.migration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.flowable.cmmn.api.history.HistoricCaseInstance; +import org.flowable.cmmn.api.migration.CaseInstanceMigrationCallback; +import org.flowable.cmmn.api.migration.CaseInstanceMigrationDocument; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.cmmn.api.repository.CaseDefinition; +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.task.api.Task; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Tests that {@link CaseInstanceMigrationCallback} receives the source case definition the instance was migrated from, + * and that callbacks implementing only the deprecated methods are still invoked (backwards compatibility). + */ +public class CaseInstanceMigrationCallbackTest extends AbstractCaseMigrationTest { + + protected static final String ONE_TASK_CASE = "org/flowable/cmmn/test/migration/one-task.cmmn.xml"; + + @AfterEach + void resetMigrationCallbacks() { + cmmnEngineConfiguration.setCaseInstanceMigrationCallbacks(null); + } + + @Test + void runtimeMigrationProvidesSourceCaseDefinition() { + RecordingCallback callback = new RecordingCallback(); + cmmnEngineConfiguration.setCaseInstanceMigrationCallbacks(Collections.singletonList(callback)); + + CaseDefinition sourceDefinition = deployCaseDefinition("test1", ONE_TASK_CASE); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", ONE_TASK_CASE); + + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrate(caseInstance.getId()); + + assertThat(callback.getRuntimeMigrations()).hasSize(1); + RecordedMigration migration = callback.getRuntimeMigrations().get(0); + assertThat(migration.getSource().getId()).isEqualTo(sourceDefinition.getId()); + assertThat(migration.getSource().getVersion()).isEqualTo(1); + assertThat(migration.getTarget().getId()).isEqualTo(destinationDefinition.getId()); + assertThat(migration.getTarget().getVersion()).isEqualTo(2); + } + + @Test + void historicMigrationProvidesSourceCaseDefinition() { + RecordingCallback callback = new RecordingCallback(); + cmmnEngineConfiguration.setCaseInstanceMigrationCallbacks(Collections.singletonList(callback)); + + CaseDefinition sourceDefinition = deployCaseDefinition("test1", ONE_TASK_CASE); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", ONE_TASK_CASE); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + cmmnMigrationService.createHistoricCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrate(caseInstance.getId()); + + assertThat(callback.getHistoricMigrations()).hasSize(1); + RecordedMigration migration = callback.getHistoricMigrations().get(0); + assertThat(migration.getSource().getId()).isEqualTo(sourceDefinition.getId()); + assertThat(migration.getSource().getVersion()).isEqualTo(1); + assertThat(migration.getTarget().getId()).isEqualTo(destinationDefinition.getId()); + assertThat(migration.getTarget().getVersion()).isEqualTo(2); + } + + @Test + void legacyCallbackStillInvokedForRuntimeMigration() { + LegacyRecordingCallback callback = new LegacyRecordingCallback(); + cmmnEngineConfiguration.setCaseInstanceMigrationCallbacks(Collections.singletonList(callback)); + + deployCaseDefinition("test1", ONE_TASK_CASE); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", ONE_TASK_CASE); + + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrate(caseInstance.getId()); + + assertThat(callback.getRuntimeTargetDefinitionIds()).containsExactly(destinationDefinition.getId()); + } + + static class RecordedMigration { + + private final CaseDefinition source; + private final CaseDefinition target; + + RecordedMigration(CaseDefinition source, CaseDefinition target) { + this.source = source; + this.target = target; + } + + public CaseDefinition getSource() { + return source; + } + + public CaseDefinition getTarget() { + return target; + } + } + + static class RecordingCallback implements CaseInstanceMigrationCallback { + + private final List runtimeMigrations = new ArrayList<>(); + private final List historicMigrations = new ArrayList<>(); + + @Override + public void caseInstanceMigrated(CaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, CaseInstanceMigrationDocument document) { + } + + @Override + public void historicCaseInstanceMigrated(HistoricCaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, + HistoricCaseInstanceMigrationDocument document) { + } + + @Override + public void caseInstanceMigrated(CaseInstance caseInstance, CaseDefinition sourceCaseDefinition, + CaseDefinition caseDefToMigrateTo, CaseInstanceMigrationDocument document) { + runtimeMigrations.add(new RecordedMigration(sourceCaseDefinition, caseDefToMigrateTo)); + } + + @Override + public void historicCaseInstanceMigrated(HistoricCaseInstance caseInstance, CaseDefinition sourceCaseDefinition, + CaseDefinition caseDefToMigrateTo, HistoricCaseInstanceMigrationDocument document) { + historicMigrations.add(new RecordedMigration(sourceCaseDefinition, caseDefToMigrateTo)); + } + + public List getRuntimeMigrations() { + return runtimeMigrations; + } + + public List getHistoricMigrations() { + return historicMigrations; + } + } + + /** Implements only the old methods to verify the new overloads delegate to them. */ + static class LegacyRecordingCallback implements CaseInstanceMigrationCallback { + + private final List runtimeTargetDefinitionIds = new ArrayList<>(); + private final List historicTargetDefinitionIds = new ArrayList<>(); + + @Override + public void caseInstanceMigrated(CaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, CaseInstanceMigrationDocument document) { + runtimeTargetDefinitionIds.add(caseDefToMigrateTo.getId()); + } + + @Override + public void historicCaseInstanceMigrated(HistoricCaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, + HistoricCaseInstanceMigrationDocument document) { + historicTargetDefinitionIds.add(caseDefToMigrateTo.getId()); + } + + public List getRuntimeTargetDefinitionIds() { + return runtimeTargetDefinitionIds; + } + + public List getHistoricTargetDefinitionIds() { + return historicTargetDefinitionIds; + } + } + +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java index 7f6355ed7dc..ff55892678e 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java @@ -449,6 +449,7 @@ protected void doMigrateProcessInstance(ProcessInstance processInstance, Process procDefToMigrateTo, document, commandContext); LOGGER.debug("Updating Process definition reference of process root execution with id:'{}' to '{}'", processInstance.getId(), procDefToMigrateTo.getId()); + String originalProcessDefinitionId = processInstanceEntity.getProcessDefinitionId(); processInstanceEntity.setProcessDefinitionId(procDefToMigrateTo.getId()); LOGGER.debug("Resolve activity executions to migrate"); @@ -500,8 +501,9 @@ protected void doMigrateProcessInstance(ProcessInstance processInstance, Process List migrationCallbacks = CommandContextUtil.getProcessEngineConfiguration(commandContext).getProcessInstanceMigrationCallbacks(); if (migrationCallbacks != null && !migrationCallbacks.isEmpty()) { + ProcessDefinition sourceProcessDefinition = ProcessDefinitionUtil.getProcessDefinition(originalProcessDefinitionId); for (ProcessInstanceMigrationCallback processInstanceMigrationCallback : migrationCallbacks) { - processInstanceMigrationCallback.processInstanceMigrated(processInstance, procDefToMigrateTo, document, commandContext); + processInstanceMigrationCallback.processInstanceMigrated(processInstance, sourceProcessDefinition, procDefToMigrateTo, document, commandContext); } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceMigrationCallback.java b/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceMigrationCallback.java index aaebcf1a9b8..b203c41bd3d 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceMigrationCallback.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceMigrationCallback.java @@ -20,5 +20,10 @@ public interface ProcessInstanceMigrationCallback { void processInstanceMigrated(ProcessInstance processInstance, ProcessDefinition procDefToMigrateTo, ProcessInstanceMigrationDocument document, CommandContext commandContext); - + + default void processInstanceMigrated(ProcessInstance processInstance, ProcessDefinition sourceProcessDefinition, + ProcessDefinition procDefToMigrateTo, ProcessInstanceMigrationDocument document, CommandContext commandContext) { + processInstanceMigrated(processInstance, procDefToMigrateTo, document, commandContext); + } + } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallbackTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallbackTest.java new file mode 100644 index 00000000000..908e05198bb --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallbackTest.java @@ -0,0 +1,135 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.engine.test.api.runtime.migration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.migration.ProcessInstanceMigrationCallback; +import org.flowable.engine.migration.ProcessInstanceMigrationDocument; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Tests that {@link ProcessInstanceMigrationCallback} receives the source process definition the instance was migrated + * from, and that a callback implementing only the deprecated method is still invoked (backwards compatibility). + */ +public class ProcessInstanceMigrationCallbackTest extends AbstractProcessInstanceMigrationTest { + + protected static final String ONE_TASK_PROCESS = "org/flowable/engine/test/api/runtime/migration/one-task-simple-process.bpmn20.xml"; + + @AfterEach + void resetMigrationCallbacks() { + processEngineConfiguration.setProcessInstanceMigrationCallbacks(null); + deleteDeployments(); + } + + @Test + public void migrationProvidesSourceProcessDefinition() { + RecordingCallback callback = new RecordingCallback(); + processEngineConfiguration.setProcessInstanceMigrationCallbacks(Collections.singletonList(callback)); + + ProcessDefinition sourceDefinition = deployProcessDefinition("my deploy", ONE_TASK_PROCESS); + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("MP"); + ProcessDefinition destinationDefinition = deployProcessDefinition("my deploy", ONE_TASK_PROCESS); + + processMigrationService.createProcessInstanceMigrationBuilder() + .migrateToProcessDefinition(destinationDefinition.getId()) + .migrate(processInstance.getId()); + + assertThat(callback.getMigrations()).hasSize(1); + RecordedMigration migration = callback.getMigrations().get(0); + assertThat(migration.getSource().getId()).isEqualTo(sourceDefinition.getId()); + assertThat(migration.getSource().getVersion()).isEqualTo(1); + assertThat(migration.getTarget().getId()).isEqualTo(destinationDefinition.getId()); + assertThat(migration.getTarget().getVersion()).isEqualTo(2); + } + + @Test + public void legacyCallbackStillInvoked() { + LegacyRecordingCallback callback = new LegacyRecordingCallback(); + processEngineConfiguration.setProcessInstanceMigrationCallbacks(Collections.singletonList(callback)); + + deployProcessDefinition("my deploy", ONE_TASK_PROCESS); + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("MP"); + ProcessDefinition destinationDefinition = deployProcessDefinition("my deploy", ONE_TASK_PROCESS); + + processMigrationService.createProcessInstanceMigrationBuilder() + .migrateToProcessDefinition(destinationDefinition.getId()) + .migrate(processInstance.getId()); + + assertThat(callback.getTargetDefinitionIds()).containsExactly(destinationDefinition.getId()); + } + + static class RecordedMigration { + + private final ProcessDefinition source; + private final ProcessDefinition target; + + RecordedMigration(ProcessDefinition source, ProcessDefinition target) { + this.source = source; + this.target = target; + } + + public ProcessDefinition getSource() { + return source; + } + + public ProcessDefinition getTarget() { + return target; + } + } + + static class RecordingCallback implements ProcessInstanceMigrationCallback { + + private final List migrations = new ArrayList<>(); + + @Override + public void processInstanceMigrated(ProcessInstance processInstance, ProcessDefinition procDefToMigrateTo, + ProcessInstanceMigrationDocument document, CommandContext commandContext) { + } + + @Override + public void processInstanceMigrated(ProcessInstance processInstance, ProcessDefinition sourceProcessDefinition, + ProcessDefinition procDefToMigrateTo, ProcessInstanceMigrationDocument document, CommandContext commandContext) { + migrations.add(new RecordedMigration(sourceProcessDefinition, procDefToMigrateTo)); + } + + public List getMigrations() { + return migrations; + } + } + + /** Implements only the deprecated method to verify the new overload delegates to it. */ + static class LegacyRecordingCallback implements ProcessInstanceMigrationCallback { + + private final List targetDefinitionIds = new ArrayList<>(); + + @Override + public void processInstanceMigrated(ProcessInstance processInstance, ProcessDefinition procDefToMigrateTo, + ProcessInstanceMigrationDocument document, CommandContext commandContext) { + targetDefinitionIds.add(procDefToMigrateTo.getId()); + } + + public List getTargetDefinitionIds() { + return targetDefinitionIds; + } + } + +}