From bc5c13e860d2b7a3e7140501e67812b9949465aa Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Thu, 24 Aug 2023 08:58:01 +0200 Subject: [PATCH] Add support for migrating historic case instances --- .../cmmn/api/CmmnMigrationService.java | 16 ++ .../CaseInstanceMigrationCallback.java | 3 + .../HistoricCaseInstanceMigrationBuilder.java | 115 ++++++++ ...HistoricCaseInstanceMigrationDocument.java | 28 ++ ...cCaseInstanceMigrationDocumentBuilder.java | 26 ++ .../cmmn/engine/CmmnEngineConfiguration.java | 2 + ...HistoricCaseInstanceMigrationBatchCmd.java | 80 ++++++ .../cmd/HistoricCaseInstanceMigrationCmd.java | 100 +++++++ .../history/DefaultCmmnHistoryManager.java | 13 + ...storicCaseInstanceMigrationJobHandler.java | 79 ++++++ ...aseInstanceMigrationDocumentConverter.java | 1 - .../CaseInstanceMigrationManager.java | 11 + .../CaseInstanceMigrationManagerImpl.java | 209 ++++++++++++++- .../migration/CmmnMigrationServiceImpl.java | 41 +++ ...toricCaseInstanceMigrationBuilderImpl.java | 101 +++++++ ...eInstanceMigrationDocumentBuilderImpl.java | 52 ++++ ...aseInstanceMigrationDocumentConverter.java | 100 +++++++ ...oricCaseInstanceMigrationDocumentImpl.java | 68 +++++ .../CaseInstanceMigrationBatchTest.java | 4 +- ...istoricCaseInstanceMigrationBatchTest.java | 248 ++++++++++++++++++ .../HistoricCaseInstanceMigrationTest.java | 186 +++++++++++++ .../SimpleHistoricCaseReactivationTest.java | 166 ++++++++++++ ...le_Migrate_Reactivation_Test_Case.cmmn.xml | 46 ++++ 23 files changed, 1691 insertions(+), 4 deletions(-) create mode 100644 modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationBuilder.java create mode 100644 modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocument.java create mode 100644 modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocumentBuilder.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationBatchCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/HistoricCaseInstanceMigrationJobHandler.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationBuilderImpl.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentBuilderImpl.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentConverter.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentImpl.java create mode 100644 modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationBatchTest.java create mode 100644 modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationTest.java create mode 100644 modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/reactivation/SimpleHistoricCaseReactivationTest.java create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/reactivation/Simple_Migrate_Reactivation_Test_Case.cmmn.xml diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnMigrationService.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnMigrationService.java index ca658de6c5e..2f58b6151bf 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnMigrationService.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnMigrationService.java @@ -18,6 +18,8 @@ import org.flowable.cmmn.api.migration.CaseInstanceMigrationBuilder; import org.flowable.cmmn.api.migration.CaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.CaseInstanceMigrationValidationResult; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationBuilder; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; /** * Service to manager case instance migrations. @@ -29,6 +31,10 @@ public interface CmmnMigrationService { CaseInstanceMigrationBuilder createCaseInstanceMigrationBuilder(); CaseInstanceMigrationBuilder createCaseInstanceMigrationBuilderFromCaseInstanceMigrationDocument(CaseInstanceMigrationDocument document); + + HistoricCaseInstanceMigrationBuilder createHistoricCaseInstanceMigrationBuilder(); + + HistoricCaseInstanceMigrationBuilder createHistoricCaseInstanceMigrationBuilderFromHistoricCaseInstanceMigrationDocument(HistoricCaseInstanceMigrationDocument document); CaseInstanceMigrationValidationResult validateMigrationForCaseInstance(String caseInstanceId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); @@ -37,14 +43,24 @@ public interface CmmnMigrationService { CaseInstanceMigrationValidationResult validateMigrationForCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); void migrateCaseInstance(String caseInstanceId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); + + void migrateHistoricCaseInstance(String caseInstanceId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument); void migrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); + + void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument); void migrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); + + void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument); Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); + + Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument); Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument caseInstanceMigrationDocument); + + Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument); CaseInstanceBatchMigrationResult getResultsOfBatchCaseInstanceMigration(String migrationBatchId); 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 5609f1b633d..bd6c82c88a2 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 @@ -12,6 +12,7 @@ */ package org.flowable.cmmn.api.migration; +import org.flowable.cmmn.api.history.HistoricCaseInstance; import org.flowable.cmmn.api.repository.CaseDefinition; import org.flowable.cmmn.api.runtime.CaseInstance; @@ -19,4 +20,6 @@ public interface CaseInstanceMigrationCallback { void caseInstanceMigrated(CaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, CaseInstanceMigrationDocument document); + void historicCaseInstanceMigrated(HistoricCaseInstance caseInstance, CaseDefinition caseDefToMigrateTo, HistoricCaseInstanceMigrationDocument document); + } diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationBuilder.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationBuilder.java new file mode 100644 index 00000000000..2d17aeb60d9 --- /dev/null +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationBuilder.java @@ -0,0 +1,115 @@ +/* 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.api.migration; + +import org.flowable.batch.api.Batch; + +public interface HistoricCaseInstanceMigrationBuilder { + + /** + * Creates a HistoricCaseInstanceMigrationBuilder using the values of a HistoricCaseInstanceMigrationDocument + * + * @param historicCaseInstanceMigrationDocument Migration document with pre-filled case information + * @return Returns the builder + * @see HistoricCaseInstanceMigrationDocument + */ + HistoricCaseInstanceMigrationBuilder fromHistoricCaseInstanceMigrationDocument(HistoricCaseInstanceMigrationDocument caseInstanceMigrationDocument); + + /** + * Specifies the case definition to migrate to, using the case definition id + * + * @param caseDefinitionId ID of the case definition to migrate to + * @return Returns the builder + * @see org.flowable.cmmn.api.repository.CaseDefinition + */ + HistoricCaseInstanceMigrationBuilder migrateToCaseDefinition(String caseDefinitionId); + + /** + * Specifies the case definition to migrate to, identified by its key and version + * + * @param caseDefinitionKey Key of the case definition to migrate to + * @param caseDefinitionVersion Version of the case to migrate to + * @return Returns the builder + * @see org.flowable.cmmn.api.repository.CaseDefinition + */ + HistoricCaseInstanceMigrationBuilder migrateToCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion); + + /** + * Specifies the case definition to migrate to, identified by its key and version and tenantId + * + * @param caseDefinitionKey Key of the case definition to migrate to + * @param caseDefinitionVersion Version of the case to migrate to + * @param caseDefinitionTenantId Tenant id of the case definition, must be part of the same tenant + * @return Returns the builder + * @see org.flowable.cmmn.api.repository.CaseDefinition + */ + HistoricCaseInstanceMigrationBuilder migrateToCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId); + + /** + * Specifies the tenantId of the case definition to migrate to + * + * @param caseDefinitionTenantId Tenant id of the case definition, must be part of the same tenant + * @return Returns the builder + */ + HistoricCaseInstanceMigrationBuilder withMigrateToCaseDefinitionTenantId(String caseDefinitionTenantId); + + /** + * Builds a HistoricCaseInstanceMigrationDocument + * + * @return Returns the builder + * @see HistoricCaseInstanceMigrationDocument + */ + HistoricCaseInstanceMigrationDocument getHistoricCaseInstanceMigrationDocument(); + + /** + * Starts the case instance migration for a case identified with the submitted caseInstanceId + * + * @param caseInstanceId + */ + void migrate(String caseInstanceId); + + /** + * Asynchronously starts the case instance migration for each case instances of a given case definition identified by the case definition id. + * + * @param caseDefinitionId + */ + void migrateHistoricCaseInstances(String caseDefinitionId); + + /** + * Starts the case instance migration for all case instances of a given case definition identified by the case definition id. + * + * @param caseDefinitionId + */ + Batch batchMigrateHistoricCaseInstances(String caseDefinitionId); + + /** + * Starts the case instance migration for all case instances of a given case definition identified by the case definition key and version (optional tenantId). + * + * @param caseDefinitionKey + * @param caseDefinitionVersion + * @param caseDefinitionTenantId + */ + void migrateHistoricCaseInstances(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId); + + /** + * Asynchronously starts the case instance migration for each case instances of a given case definition identified by the case definition key and version (optional tenantId). + * + * @param caseDefinitionKey + * @param caseDefinitionVersion + * @param caseDefinitionTenantId + * @return an id of the created batch entity + */ + Batch batchMigrateHistoricCaseInstances(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId); + +} diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocument.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocument.java new file mode 100644 index 00000000000..7375c865ce3 --- /dev/null +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocument.java @@ -0,0 +1,28 @@ +/* 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.api.migration; + +public interface HistoricCaseInstanceMigrationDocument { + + String getMigrateToCaseDefinitionId(); + + String getMigrateToCaseDefinitionKey(); + + Integer getMigrateToCaseDefinitionVersion(); + + String getMigrateToCaseDefinitionTenantId(); + + String asJsonString(); + +} diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocumentBuilder.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocumentBuilder.java new file mode 100644 index 00000000000..1400ad40b3d --- /dev/null +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/HistoricCaseInstanceMigrationDocumentBuilder.java @@ -0,0 +1,26 @@ +/* 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.api.migration; + +public interface HistoricCaseInstanceMigrationDocumentBuilder { + + HistoricCaseInstanceMigrationDocumentBuilder setCaseDefinitionToMigrateTo(String caseDefinitionId); + + HistoricCaseInstanceMigrationDocumentBuilder setCaseDefinitionToMigrateTo(String caseDefinitionKey, Integer caseDefinitionVersion); + + HistoricCaseInstanceMigrationDocumentBuilder setTenantId(String caseDefinitionTenantId); + + HistoricCaseInstanceMigrationDocument build(); + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java index 214085deb13..e8308fd9ea8 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java @@ -104,6 +104,7 @@ import org.flowable.cmmn.engine.impl.job.CaseInstanceMigrationStatusJobHandler; import org.flowable.cmmn.engine.impl.job.CmmnHistoryCleanupJobHandler; import org.flowable.cmmn.engine.impl.job.ExternalWorkerTaskCompleteJobHandler; +import org.flowable.cmmn.engine.impl.job.HistoricCaseInstanceMigrationJobHandler; import org.flowable.cmmn.engine.impl.job.TriggerTimerEventJobHandler; import org.flowable.cmmn.engine.impl.listener.CmmnListenerFactory; import org.flowable.cmmn.engine.impl.listener.CmmnListenerNotificationHelper; @@ -1645,6 +1646,7 @@ public void initJobHandlers() { jobHandlers.put(ExternalWorkerTaskCompleteJobHandler.TYPE, new ExternalWorkerTaskCompleteJobHandler(this)); addJobHandler(new CaseInstanceMigrationJobHandler()); addJobHandler(new CaseInstanceMigrationStatusJobHandler()); + addJobHandler(new HistoricCaseInstanceMigrationJobHandler()); addJobHandler(new ComputeDeleteHistoricCaseInstanceIdsJobHandler()); addJobHandler(new ComputeDeleteHistoricCaseInstanceStatusJobHandler()); addJobHandler(new DeleteHistoricCaseInstanceIdsJobHandler()); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationBatchCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationBatchCmd.java new file mode 100644 index 00000000000..197544b5476 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationBatchCmd.java @@ -0,0 +1,80 @@ +/* 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.engine.impl.cmd; + +import org.flowable.batch.api.Batch; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationManager; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +public class HistoricCaseInstanceMigrationBatchCmd implements Command { + + protected CmmnEngineConfiguration cmmnEngineConfiguration; + + protected String caseDefinitionId; + protected String caseDefinitionKey; + protected int caseDefinitionVersion; + protected String caseDefinitionTenantId; + protected HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument; + + public HistoricCaseInstanceMigrationBatchCmd(HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument, String caseDefinitionId, + CmmnEngineConfiguration cmmnEngineConfiguration) { + + if (caseDefinitionId == null) { + throw new FlowableException("Must specify a case definition id to migrate"); + } + if (historicCaseInstanceMigrationDocument == null) { + throw new FlowableException("Must specify a historic case instance migration document to migrate"); + } + this.caseDefinitionId = caseDefinitionId; + this.historicCaseInstanceMigrationDocument = historicCaseInstanceMigrationDocument; + this.cmmnEngineConfiguration = cmmnEngineConfiguration; + } + + public HistoricCaseInstanceMigrationBatchCmd(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, + HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument, CmmnEngineConfiguration cmmnEngineConfiguration) { + + if (caseDefinitionKey == null) { + throw new FlowableException("Must specify a case definition id to migrate"); + } + if (caseDefinitionTenantId == null) { + throw new FlowableException("Must specify a case definition tenant id to migrate"); + } + if (historicCaseInstanceMigrationDocument == null) { + throw new FlowableException("Must specify a historic case instance migration document to migrate"); + } + this.caseDefinitionKey = caseDefinitionKey; + this.caseDefinitionVersion = caseDefinitionVersion; + this.caseDefinitionTenantId = caseDefinitionTenantId; + this.historicCaseInstanceMigrationDocument = historicCaseInstanceMigrationDocument; + this.cmmnEngineConfiguration = cmmnEngineConfiguration; + } + + @Override + public Batch execute(CommandContext commandContext) { + CaseInstanceMigrationManager migrationManager = cmmnEngineConfiguration.getCaseInstanceMigrationManager(); + + if (caseDefinitionId != null) { + return migrationManager.batchMigrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionId, historicCaseInstanceMigrationDocument, commandContext); + } else if (caseDefinitionKey != null && caseDefinitionVersion >= 0) { + return migrationManager.batchMigrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, + historicCaseInstanceMigrationDocument, commandContext); + } else { + throw new FlowableException("Cannot migrate historic case instances, not enough information"); + } + } +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationCmd.java new file mode 100644 index 00000000000..f14e0e976fd --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/HistoricCaseInstanceMigrationCmd.java @@ -0,0 +1,100 @@ +/* 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.engine.impl.cmd; + +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationManager; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +public class HistoricCaseInstanceMigrationCmd implements Command { + + protected CmmnEngineConfiguration cmmnEngineConfiguration; + + protected String caseInstanceId; + protected String caseDefinitionId; + protected String caseDefinitionKey; + protected int caseDefinitionVersion; + protected String caseDefinitionTenantId; + protected HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument; + + public HistoricCaseInstanceMigrationCmd(String caseInstanceId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument, + CmmnEngineConfiguration cmmnEngineConfiguration) { + + if (caseInstanceId == null) { + throw new FlowableException("Must specify a historic case instance id to migrate"); + } + if (historicCaseInstanceMigrationDocument == null) { + throw new FlowableException("Must specify a historic case instance migration document to migrate"); + } + + this.caseInstanceId = caseInstanceId; + this.historicCaseInstanceMigrationDocument = historicCaseInstanceMigrationDocument; + this.cmmnEngineConfiguration = cmmnEngineConfiguration; + } + + public HistoricCaseInstanceMigrationCmd(HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument, String caseDefinitionId, + CmmnEngineConfiguration cmmnEngineConfiguration) { + + if (caseDefinitionId == null) { + throw new FlowableException("Must specify a case definition id to migrate"); + } + if (historicCaseInstanceMigrationDocument == null) { + throw new FlowableException("Must specify a historic case instance migration document to migrate"); + } + + this.caseDefinitionId = caseDefinitionId; + this.historicCaseInstanceMigrationDocument = historicCaseInstanceMigrationDocument; + this.cmmnEngineConfiguration = cmmnEngineConfiguration; + } + + public HistoricCaseInstanceMigrationCmd(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, + HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument, CmmnEngineConfiguration cmmnEngineConfiguration) { + + if (caseDefinitionKey == null) { + throw new FlowableException("Must specify a case definition id to migrate"); + } + if (caseDefinitionTenantId == null) { + throw new FlowableException("Must specify a case definition tenant id to migrate"); + } + if (historicCaseInstanceMigrationDocument == null) { + throw new FlowableException("Must specify a historic case instance migration document to migrate"); + } + + this.caseDefinitionKey = caseDefinitionKey; + this.caseDefinitionVersion = caseDefinitionVersion; + this.caseDefinitionTenantId = caseDefinitionTenantId; + this.historicCaseInstanceMigrationDocument = historicCaseInstanceMigrationDocument; + this.cmmnEngineConfiguration = cmmnEngineConfiguration; + } + + @Override + public Void execute(CommandContext commandContext) { + CaseInstanceMigrationManager migrationManager = cmmnEngineConfiguration.getCaseInstanceMigrationManager(); + + if (caseInstanceId != null) { + migrationManager.migrateHistoricCaseInstance(caseInstanceId, historicCaseInstanceMigrationDocument, commandContext); + } else if (caseDefinitionId != null) { + migrationManager.migrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionId, historicCaseInstanceMigrationDocument, commandContext); + } else if (caseDefinitionKey != null && caseDefinitionVersion >= 0) { + migrationManager.migrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionKey, caseDefinitionVersion, + caseDefinitionTenantId, historicCaseInstanceMigrationDocument, commandContext); + } else { + throw new FlowableException("Cannot migrate case(es), not enough information"); + } + return null; + } +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/DefaultCmmnHistoryManager.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/DefaultCmmnHistoryManager.java index a2aadbc3e47..5a0d8f1ce96 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/DefaultCmmnHistoryManager.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/DefaultCmmnHistoryManager.java @@ -18,6 +18,7 @@ import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; +import org.flowable.cmmn.api.history.HistoricMilestoneInstance; import org.flowable.cmmn.api.history.HistoricPlanItemInstance; import org.flowable.cmmn.api.repository.CaseDefinition; import org.flowable.cmmn.engine.CmmnEngineConfiguration; @@ -425,6 +426,18 @@ public void updateCaseDefinitionIdInHistory(CaseDefinition caseDefinition, CaseI historicPlanItemInstanceEntityManager.update(planItemEntity); } } + + HistoricMilestoneInstanceQueryImpl historicMilestoneInstanceQuery = new HistoricMilestoneInstanceQueryImpl(); + historicMilestoneInstanceQuery.milestoneInstanceCaseInstanceId(caseInstance.getId()); + HistoricMilestoneInstanceEntityManager historicMilestoneInstanceEntityManager = cmmnEngineConfiguration.getHistoricMilestoneInstanceEntityManager(); + List historicMilestoneInstances = historicMilestoneInstanceEntityManager.findHistoricMilestoneInstancesByQueryCriteria(historicMilestoneInstanceQuery); + if (historicMilestoneInstances != null) { + for (HistoricMilestoneInstance historicMilestoneInstance : historicMilestoneInstances) { + HistoricMilestoneInstanceEntity milestoneEntity = (HistoricMilestoneInstanceEntity) historicMilestoneInstance; + milestoneEntity.setCaseDefinitionId(caseDefinition.getId()); + historicMilestoneInstanceEntityManager.update(milestoneEntity); + } + } } } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/HistoricCaseInstanceMigrationJobHandler.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/HistoricCaseInstanceMigrationJobHandler.java new file mode 100644 index 00000000000..aaad65be3c0 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/HistoricCaseInstanceMigrationJobHandler.java @@ -0,0 +1,79 @@ +/* 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.engine.impl.job; + +import org.flowable.batch.api.Batch; +import org.flowable.batch.api.BatchPart; +import org.flowable.batch.api.BatchService; +import org.flowable.cmmn.api.migration.CaseInstanceBatchMigrationResult; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationManager; +import org.flowable.cmmn.engine.impl.migration.HistoricCaseInstanceMigrationDocumentImpl; +import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.api.delegate.VariableScope; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class HistoricCaseInstanceMigrationJobHandler extends AbstractCaseInstanceMigrationJobHandler { + + public static final String TYPE = "historic-case-migration"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + CmmnEngineConfiguration engineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + BatchService batchService = engineConfiguration.getBatchServiceConfiguration().getBatchService(); + CaseInstanceMigrationManager migrationManager = engineConfiguration.getCaseInstanceMigrationManager(); + + String batchPartId = getBatchPartIdFromHandlerCfg(configuration); + BatchPart batchPart = batchService.getBatchPart(batchPartId); + Batch batch = batchService.getBatch(batchPart.getBatchId()); + HistoricCaseInstanceMigrationDocument migrationDocument = HistoricCaseInstanceMigrationDocumentImpl.fromJson( + batch.getBatchDocumentJson(engineConfiguration.getEngineCfgKey())); + + String exceptionMessage = null; + try { + migrationManager.migrateHistoricCaseInstance(batchPart.getScopeId(), migrationDocument, commandContext); + } catch (FlowableException e) { + exceptionMessage = e.getMessage(); + } + + String resultAsJsonString = prepareResultAsJsonString(exceptionMessage); + + if (exceptionMessage != null) { + batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); + } else { + batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); + } + } + + protected static String prepareResultAsJsonString(String exceptionMessage) { + ObjectNode objectNode = getObjectMapper().createObjectNode(); + if (exceptionMessage == null) { + objectNode.put(BATCH_RESULT_STATUS_LABEL, CaseInstanceBatchMigrationResult.RESULT_SUCCESS); + } else { + objectNode.put(BATCH_RESULT_STATUS_LABEL, CaseInstanceBatchMigrationResult.RESULT_FAIL); + objectNode.put(BATCH_RESULT_MESSAGE_LABEL, exceptionMessage); + } + return objectNode.toString(); + } + +} \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationDocumentConverter.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationDocumentConverter.java index 5a89b251f3a..3299c475c0a 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationDocumentConverter.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationDocumentConverter.java @@ -45,7 +45,6 @@ public class CaseInstanceMigrationDocumentConverter implements CaseInstanceMigra protected static ObjectMapper objectMapper = new ObjectMapper(); public static JsonNode convertToJson(CaseInstanceMigrationDocument caseInstanceMigrationDocument) { - ObjectNode documentNode = objectMapper.createObjectNode(); if (caseInstanceMigrationDocument.getMigrateToCaseDefinitionId() != null) { diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManager.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManager.java index 213c40ec83a..7b0aabdf0b3 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManager.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManager.java @@ -15,6 +15,7 @@ import org.flowable.batch.api.Batch; import org.flowable.cmmn.api.migration.CaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.CaseInstanceMigrationValidationResult; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; import org.flowable.common.engine.impl.interceptor.CommandContext; public interface CaseInstanceMigrationManager { @@ -26,12 +27,22 @@ public interface CaseInstanceMigrationManager { CaseInstanceMigrationValidationResult validateMigrateCaseInstance(String caseInstanceId, CaseInstanceMigrationDocument document, CommandContext commandContext); void migrateCaseInstance(String caseInstanceId, CaseInstanceMigrationDocument document, CommandContext commandContext); + + void migrateHistoricCaseInstance(String caseInstanceId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext); void migrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument document, CommandContext commandContext); + + void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext); void migrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument document, CommandContext commandContext); + + void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext); Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument document, CommandContext commandContext); + + Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext); Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument document, CommandContext commandContext); + + Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext); } 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 f7efe19b22c..732e4d30a06 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 @@ -21,11 +21,15 @@ import org.flowable.batch.api.Batch; import org.flowable.batch.api.BatchPart; import org.flowable.batch.api.BatchService; +import org.flowable.cmmn.api.history.HistoricCaseInstance; +import org.flowable.cmmn.api.history.HistoricMilestoneInstance; +import org.flowable.cmmn.api.history.HistoricPlanItemInstance; import org.flowable.cmmn.api.migration.ActivatePlanItemDefinitionMapping; import org.flowable.cmmn.api.migration.CaseInstanceBatchMigrationResult; import org.flowable.cmmn.api.migration.CaseInstanceMigrationCallback; import org.flowable.cmmn.api.migration.CaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.CaseInstanceMigrationValidationResult; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.MoveToAvailablePlanItemDefinitionMapping; import org.flowable.cmmn.api.migration.PlanItemDefinitionMapping; import org.flowable.cmmn.api.migration.RemoveWaitingForRepetitionPlanItemDefinitionMapping; @@ -33,13 +37,24 @@ import org.flowable.cmmn.api.migration.WaitingForRepetitionPlanItemDefinitionMapping; import org.flowable.cmmn.api.repository.CaseDefinition; import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.api.runtime.CaseInstanceState; import org.flowable.cmmn.engine.CmmnEngineConfiguration; import org.flowable.cmmn.engine.impl.history.CmmnHistoryManager; +import org.flowable.cmmn.engine.impl.history.HistoricCaseInstanceQueryImpl; +import org.flowable.cmmn.engine.impl.history.HistoricMilestoneInstanceQueryImpl; +import org.flowable.cmmn.engine.impl.history.HistoricPlanItemInstanceQueryImpl; import org.flowable.cmmn.engine.impl.job.CaseInstanceMigrationJobHandler; import org.flowable.cmmn.engine.impl.job.CaseInstanceMigrationStatusJobHandler; +import org.flowable.cmmn.engine.impl.job.HistoricCaseInstanceMigrationJobHandler; import org.flowable.cmmn.engine.impl.persistence.entity.CaseDefinitionEntityManager; import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity; import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntityManager; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricCaseInstanceEntity; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricCaseInstanceEntityManager; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricMilestoneInstanceEntity; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricMilestoneInstanceEntityManager; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricPlanItemInstanceEntity; +import org.flowable.cmmn.engine.impl.persistence.entity.HistoricPlanItemInstanceEntityManager; import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil; import org.flowable.cmmn.engine.impl.runtime.AbstractCmmnDynamicStateManager; import org.flowable.cmmn.engine.impl.runtime.CaseInstanceChangeState; @@ -57,6 +72,10 @@ import org.flowable.job.service.TimerJobService; import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.service.HistoricTaskService; +import org.flowable.task.service.impl.HistoricTaskInstanceQueryImpl; +import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity; public class CaseInstanceMigrationManagerImpl extends AbstractCmmnDynamicStateManager implements CaseInstanceMigrationManager { @@ -172,12 +191,34 @@ public void migrateCaseInstance(String caseInstanceId, CaseInstanceMigrationDocu CaseDefinition caseDefinitionToMigrateTo = resolveCaseDefinition(document, commandContext); doMigrateCaseInstance(caseInstance, caseDefinitionToMigrateTo, document, commandContext); } + + @Override + public void migrateHistoricCaseInstance(String caseInstanceId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + HistoricCaseInstanceEntityManager historicCaseInstanceEntityManager = CommandContextUtil.getHistoricCaseInstanceEntityManager(commandContext); + HistoricCaseInstanceEntity caseInstance = historicCaseInstanceEntityManager.findById(caseInstanceId); + if (caseInstance == null) { + throw new FlowableException("Cannot find the historic case instance to migrate, with id" + caseInstanceId); + } + + if (!CaseInstanceState.END_STATES.contains(caseInstance.getState())) { + throw new FlowableException("Historic case instance has not ended and can only be migrated with the regular case instance migrate method (migrateCaseInstance) for id " + caseInstanceId); + } + + CaseDefinition caseDefinitionToMigrateTo = resolveCaseDefinition(document, commandContext); + doMigrateHistoricCaseInstance(caseInstance, caseDefinitionToMigrateTo, document, commandContext); + } @Override public void migrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument document, CommandContext commandContext) { CaseDefinition caseDefinition = resolveCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, commandContext); migrateCaseInstancesOfCaseDefinition(caseDefinition.getId(), document, commandContext); } + + @Override + public void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + CaseDefinition caseDefinition = resolveCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, commandContext); + migrateHistoricCaseInstancesOfCaseDefinition(caseDefinition.getId(), document, commandContext); + } @Override public void migrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument document, CommandContext commandContext) { @@ -194,6 +235,22 @@ public void migrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseIn doMigrateCaseInstance((CaseInstanceEntity) caseInstance, caseDefinitionToMigrateTo, document, commandContext); } } + + @Override + public void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + CaseDefinition caseDefinitionToMigrateTo = resolveCaseDefinition(document, commandContext); + if (caseDefinitionToMigrateTo == null) { + throw new FlowableException("Cannot find the case definition to migrate to, identified by " + printCaseDefinitionIdentifierMessage(document)); + } + + HistoricCaseInstanceQueryImpl historicCaseInstanceQuery = new HistoricCaseInstanceQueryImpl(commandContext, cmmnEngineConfiguration).caseDefinitionId(caseDefinitionId).finished(); + HistoricCaseInstanceEntityManager historicCaseInstanceEntityManager = cmmnEngineConfiguration.getHistoricCaseInstanceEntityManager(); + List historicCaseInstances = historicCaseInstanceEntityManager.findByCriteria(historicCaseInstanceQuery); + + for (HistoricCaseInstance historicCaseInstance : historicCaseInstances) { + doMigrateHistoricCaseInstance((HistoricCaseInstanceEntity) historicCaseInstance, caseDefinitionToMigrateTo, document, commandContext); + } + } protected void doMigrateCaseInstance(CaseInstanceEntity caseInstance, CaseDefinition caseDefinitionToMigrateTo, CaseInstanceMigrationDocument document, CommandContext commandContext) { LOGGER.debug("Start migration of case instance with Id:'{}' to case definition identified by {}", caseInstance.getId(), printCaseDefinitionIdentifierMessage(document)); @@ -233,6 +290,43 @@ protected void doMigrateCaseInstance(CaseInstanceEntity caseInstance, CaseDefini } } } + + protected void doMigrateHistoricCaseInstance(HistoricCaseInstanceEntity historicCaseInstance, CaseDefinition caseDefinitionToMigrateTo, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + LOGGER.debug("Start migration of historic case instance with Id:'{}' to case definition identified by {}", historicCaseInstance.getId(), printCaseDefinitionIdentifierMessage(document)); + + String destinationTenantId = caseDefinitionToMigrateTo.getTenantId(); + if (!Objects.equals(historicCaseInstance.getTenantId(), destinationTenantId)) { + + CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + if (cmmnEngineConfiguration.isFallbackToDefaultTenant() && cmmnEngineConfiguration.getDefaultTenantProvider() != null) { + + if (!Objects.equals(destinationTenantId, cmmnEngineConfiguration.getDefaultTenantProvider().getDefaultTenant(historicCaseInstance.getId(), ScopeTypes.CMMN, caseDefinitionToMigrateTo.getKey()))) { + throw new FlowableException("Tenant mismatch between Historic Case Instance ('" + historicCaseInstance.getTenantId() + "') and Case Definition ('" + destinationTenantId + "') to migrate to"); + } + + } else { + throw new FlowableException("Tenant mismatch between Historic Case Instance ('" + historicCaseInstance.getTenantId() + "') and Case Definition ('" + destinationTenantId + "') to migrate to"); + } + } + + LOGGER.debug("Updating case definition reference of case root execution with id:'{}' to '{}'", historicCaseInstance.getId(), caseDefinitionToMigrateTo.getId()); + historicCaseInstance.setCaseDefinitionId(caseDefinitionToMigrateTo.getId()); + historicCaseInstance.setCaseDefinitionKey(caseDefinitionToMigrateTo.getKey()); + historicCaseInstance.setCaseDefinitionName(caseDefinitionToMigrateTo.getName()); + historicCaseInstance.setCaseDefinitionVersion(caseDefinitionToMigrateTo.getVersion()); + historicCaseInstance.setCaseDefinitionDeploymentId(caseDefinitionToMigrateTo.getDeploymentId()); + CommandContextUtil.getHistoricCaseInstanceEntityManager(commandContext).update(historicCaseInstance); + + LOGGER.debug("Updating case definition reference in history"); + changeCaseDefinitionReferenceForHistoricCaseInstance(historicCaseInstance, caseDefinitionToMigrateTo, commandContext); + + List migrationCallbacks = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getCaseInstanceMigrationCallbacks(); + if (migrationCallbacks != null && !migrationCallbacks.isEmpty()) { + for (CaseInstanceMigrationCallback caseInstanceMigrationCallback : migrationCallbacks) { + caseInstanceMigrationCallback.historicCaseInstanceMigrated(historicCaseInstance, caseDefinitionToMigrateTo, document); + } + } + } protected ChangePlanItemStateBuilderImpl prepareChangeStateBuilder(CaseInstance caseInstance, CaseDefinition caseDefinitionToMigrateTo, CaseInstanceMigrationDocument document, CommandContext commandContext) { @@ -283,16 +377,62 @@ protected void changeCaseDefinitionReferenceOfHistory(CaseInstanceEntity caseIns CmmnHistoryManager historyManager = CommandContextUtil.getCmmnHistoryManager(commandContext); historyManager.updateCaseDefinitionIdInHistory(caseDefinitionToMigrateTo, caseInstance); } + + protected void changeCaseDefinitionReferenceForHistoricCaseInstance(HistoricCaseInstanceEntity historicCaseInstance, CaseDefinition caseDefinitionToMigrateTo, CommandContext commandContext) { + if (CommandContextUtil.getCmmnEngineConfiguration(commandContext).getCmmnHistoryConfigurationSettings().isHistoryEnabled(caseDefinitionToMigrateTo.getId())) { + HistoricTaskService historicTaskService = cmmnEngineConfiguration.getTaskServiceConfiguration().getHistoricTaskService(); + HistoricTaskInstanceQueryImpl taskQuery = new HistoricTaskInstanceQueryImpl(); + taskQuery.caseInstanceId(historicCaseInstance.getId()); + List historicTasks = historicTaskService.findHistoricTaskInstancesByQueryCriteria(taskQuery); + if (historicTasks != null) { + for (HistoricTaskInstance historicTaskInstance : historicTasks) { + HistoricTaskInstanceEntity taskEntity = (HistoricTaskInstanceEntity) historicTaskInstance; + taskEntity.setScopeDefinitionId(caseDefinitionToMigrateTo.getId()); + historicTaskService.updateHistoricTask(taskEntity, true); + } + } + + // because of upgrade runtimeActivity instances can be only subset of historicActivity instances + HistoricPlanItemInstanceQueryImpl historicPlanItemQuery = new HistoricPlanItemInstanceQueryImpl(); + historicPlanItemQuery.planItemInstanceCaseInstanceId(historicCaseInstance.getId()); + HistoricPlanItemInstanceEntityManager historicPlanItemInstanceEntityManager = cmmnEngineConfiguration.getHistoricPlanItemInstanceEntityManager(); + List historicPlanItems = historicPlanItemInstanceEntityManager.findByCriteria(historicPlanItemQuery); + if (historicPlanItems != null) { + for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItems) { + HistoricPlanItemInstanceEntity planItemEntity = (HistoricPlanItemInstanceEntity) historicPlanItemInstance; + planItemEntity.setCaseDefinitionId(caseDefinitionToMigrateTo.getId()); + historicPlanItemInstanceEntityManager.update(planItemEntity); + } + } + + HistoricMilestoneInstanceQueryImpl historicMilestoneInstanceQuery = new HistoricMilestoneInstanceQueryImpl(); + historicMilestoneInstanceQuery.milestoneInstanceCaseInstanceId(historicCaseInstance.getId()); + HistoricMilestoneInstanceEntityManager historicMilestoneInstanceEntityManager = cmmnEngineConfiguration.getHistoricMilestoneInstanceEntityManager(); + List historicMilestoneInstances = historicMilestoneInstanceEntityManager.findHistoricMilestoneInstancesByQueryCriteria(historicMilestoneInstanceQuery); + if (historicMilestoneInstances != null) { + for (HistoricMilestoneInstance historicMilestoneInstance : historicMilestoneInstances) { + HistoricMilestoneInstanceEntity milestoneEntity = (HistoricMilestoneInstanceEntity) historicMilestoneInstance; + milestoneEntity.setCaseDefinitionId(caseDefinitionToMigrateTo.getId()); + historicMilestoneInstanceEntityManager.update(milestoneEntity); + } + } + } + } @Override public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument document, CommandContext commandContext) { CaseDefinition caseDefinition = resolveCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, commandContext); return batchMigrateCaseInstancesOfCaseDefinition(caseDefinition.getId(), document, commandContext); } + + @Override + public Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + CaseDefinition caseDefinition = resolveCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, commandContext); + return batchMigrateHistoricCaseInstancesOfCaseDefinition(caseDefinition.getId(), document, commandContext); + } @Override public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument document, CommandContext commandContext) { - CaseDefinition caseDefinition = resolveCaseDefinition(document, commandContext); CmmnEngineConfiguration engineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(); @@ -339,6 +479,56 @@ public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, return batch; } + + @Override + public Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + CaseDefinition caseDefinition = resolveCaseDefinition(document, commandContext); + + CmmnEngineConfiguration engineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(); + HistoricCaseInstanceQueryImpl historicCaseInstanceQuery = new HistoricCaseInstanceQueryImpl(commandContext, cmmnEngineConfiguration).caseDefinitionId(caseDefinitionId).finished(); + List historicCaseInstances = engineConfiguration.getHistoricCaseInstanceEntityManager() + .findByCriteria(historicCaseInstanceQuery); + + BatchService batchService = engineConfiguration.getBatchServiceConfiguration().getBatchService(); + Batch batch = batchService.createBatchBuilder().batchType(Batch.CASE_MIGRATION_TYPE) + .searchKey(caseDefinitionId) + .searchKey2(caseDefinition.getId()) + .status(CaseInstanceBatchMigrationResult.STATUS_IN_PROGRESS) + .batchDocumentJson(document.asJsonString()) + .create(); + + JobService jobService = engineConfiguration.getJobServiceConfiguration().getJobService(); + for (HistoricCaseInstance historicCaseInstance : historicCaseInstances) { + BatchPart batchPart = batchService.createBatchPart(batch, CaseInstanceBatchMigrationResult.STATUS_WAITING, + historicCaseInstance.getId(), null, ScopeTypes.CMMN); + + JobEntity job = jobService.createJob(); + job.setJobHandlerType(HistoricCaseInstanceMigrationJobHandler.TYPE); + job.setScopeId(historicCaseInstance.getId()); + job.setScopeType(ScopeTypes.CMMN); + job.setJobHandlerConfiguration(HistoricCaseInstanceMigrationJobHandler.getHandlerCfgForBatchPartId(batchPart.getId())); + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + + if (!historicCaseInstances.isEmpty()) { + TimerJobService timerJobService = engineConfiguration.getJobServiceConfiguration().getTimerJobService(); + TimerJobEntity timerJob = timerJobService.createTimerJob(); + timerJob.setJobType(JobEntity.JOB_TYPE_TIMER); + timerJob.setRevision(1); + timerJob.setJobHandlerType(CaseInstanceMigrationStatusJobHandler.TYPE); + timerJob.setJobHandlerConfiguration(HistoricCaseInstanceMigrationJobHandler.getHandlerCfgForBatchId(batch.getId())); + timerJob.setScopeType(ScopeTypes.CMMN); + + BusinessCalendar businessCalendar = engineConfiguration.getBusinessCalendarManager().getBusinessCalendar(CycleBusinessCalendar.NAME); + timerJob.setDuedate(businessCalendar.resolveDuedate(engineConfiguration.getBatchStatusTimeCycleConfig())); + timerJob.setRepeat(engineConfiguration.getBatchStatusTimeCycleConfig()); + + timerJobService.scheduleTimerJob(timerJob); + } + + return batch; + } @Override protected boolean isDirectPlanItemDefinitionMigration(PlanItemDefinition currentPlanItemDefinition, PlanItemDefinition newPlanItemDefinition) { @@ -370,6 +560,16 @@ protected CaseDefinition resolveCaseDefinition(CaseInstanceMigrationDocument doc return resolveCaseDefinition(document.getMigrateToCaseDefinitionKey(), document.getMigrateToCaseDefinitionVersion(), document.getMigrateToCaseDefinitionTenantId(), commandContext); } } + + protected CaseDefinition resolveCaseDefinition(HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) { + if (document.getMigrateToCaseDefinitionId() != null) { + CaseDefinitionEntityManager caseDefinitionEntityManager = CommandContextUtil.getCaseDefinitionEntityManager(commandContext); + return caseDefinitionEntityManager.findById(document.getMigrateToCaseDefinitionId()); + + } else { + return resolveCaseDefinition(document.getMigrateToCaseDefinitionKey(), document.getMigrateToCaseDefinitionVersion(), document.getMigrateToCaseDefinitionTenantId(), commandContext); + } + } protected String printCaseDefinitionIdentifierMessage(CaseInstanceMigrationDocument document) { String id = document.getMigrateToCaseDefinitionId(); @@ -379,4 +579,11 @@ protected String printCaseDefinitionIdentifierMessage(CaseInstanceMigrationDocum return id != null ? "[id:'" + id + "']" : "[key:'" + key + "', version:'" + version + "', tenantId:'" + tenantId + "']"; } + protected String printCaseDefinitionIdentifierMessage(HistoricCaseInstanceMigrationDocument document) { + String id = document.getMigrateToCaseDefinitionId(); + String key = document.getMigrateToCaseDefinitionKey(); + Integer version = document.getMigrateToCaseDefinitionVersion(); + String tenantId = document.getMigrateToCaseDefinitionTenantId(); + return id != null ? "[id:'" + id + "']" : "[key:'" + key + "', version:'" + version + "', tenantId:'" + tenantId + "']"; + } } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CmmnMigrationServiceImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CmmnMigrationServiceImpl.java index 7eafb19a2a3..f0b89d83c0e 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CmmnMigrationServiceImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CmmnMigrationServiceImpl.java @@ -19,11 +19,15 @@ import org.flowable.cmmn.api.migration.CaseInstanceMigrationBuilder; import org.flowable.cmmn.api.migration.CaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.CaseInstanceMigrationValidationResult; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationBuilder; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; import org.flowable.cmmn.engine.CmmnEngineConfiguration; import org.flowable.cmmn.engine.impl.cmd.CaseInstanceMigrationBatchCmd; import org.flowable.cmmn.engine.impl.cmd.CaseInstanceMigrationCmd; import org.flowable.cmmn.engine.impl.cmd.CaseInstanceMigrationValidationCmd; import org.flowable.cmmn.engine.impl.cmd.GetCaseInstanceMigrationBatchResultCmd; +import org.flowable.cmmn.engine.impl.cmd.HistoricCaseInstanceMigrationBatchCmd; +import org.flowable.cmmn.engine.impl.cmd.HistoricCaseInstanceMigrationCmd; import org.flowable.common.engine.impl.service.CommonEngineServiceImpl; /** @@ -44,6 +48,16 @@ public CaseInstanceMigrationBuilder createCaseInstanceMigrationBuilder() { public CaseInstanceMigrationBuilder createCaseInstanceMigrationBuilderFromCaseInstanceMigrationDocument(CaseInstanceMigrationDocument document) { return createCaseInstanceMigrationBuilder().fromCaseInstanceMigrationDocument(document); } + + @Override + public HistoricCaseInstanceMigrationBuilder createHistoricCaseInstanceMigrationBuilder() { + return new HistoricCaseInstanceMigrationBuilderImpl(this); + } + + @Override + public HistoricCaseInstanceMigrationBuilder createHistoricCaseInstanceMigrationBuilderFromHistoricCaseInstanceMigrationDocument(HistoricCaseInstanceMigrationDocument document) { + return createHistoricCaseInstanceMigrationBuilder().fromHistoricCaseInstanceMigrationDocument(document); + } @Override public CaseInstanceMigrationValidationResult validateMigrationForCaseInstance(String caseInstanceId, CaseInstanceMigrationDocument caseInstanceMigrationDocument) { @@ -65,28 +79,55 @@ public CaseInstanceMigrationValidationResult validateMigrationForCaseInstancesOf public void migrateCaseInstance(String caseInstanceId, CaseInstanceMigrationDocument caseInstanceMigrationDocument) { commandExecutor.execute(new CaseInstanceMigrationCmd(caseInstanceId, caseInstanceMigrationDocument, configuration)); } + + @Override + public void migrateHistoricCaseInstance(String caseInstanceId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + commandExecutor.execute(new HistoricCaseInstanceMigrationCmd(caseInstanceId, historicCaseInstanceMigrationDocument, configuration)); + } @Override public void migrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument caseInstanceMigrationDocument) { commandExecutor.execute(new CaseInstanceMigrationCmd(caseInstanceMigrationDocument, caseDefinitionId, configuration)); } + + @Override + public void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + commandExecutor.execute(new HistoricCaseInstanceMigrationCmd(historicCaseInstanceMigrationDocument, caseDefinitionId, configuration)); + } @Override public void migrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument caseInstanceMigrationDocument) { commandExecutor.execute(new CaseInstanceMigrationCmd(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, caseInstanceMigrationDocument, configuration)); } + + @Override + public void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + commandExecutor.execute(new HistoricCaseInstanceMigrationCmd(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, + historicCaseInstanceMigrationDocument, configuration)); + } @Override public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, CaseInstanceMigrationDocument caseInstanceMigrationDocument) { return commandExecutor.execute(new CaseInstanceMigrationBatchCmd(caseInstanceMigrationDocument, caseDefinitionId, configuration)); } + + @Override + public Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + return commandExecutor.execute(new HistoricCaseInstanceMigrationBatchCmd(historicCaseInstanceMigrationDocument, caseDefinitionId, configuration)); + } @Override public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument caseInstanceMigrationDocument) { return commandExecutor.execute(new CaseInstanceMigrationBatchCmd(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, caseInstanceMigrationDocument, configuration)); } + + @Override + public Batch batchMigrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + return commandExecutor.execute(new HistoricCaseInstanceMigrationBatchCmd(caseDefinitionKey, caseDefinitionVersion, + caseDefinitionTenantId, historicCaseInstanceMigrationDocument, configuration)); + } @Override public CaseInstanceBatchMigrationResult getResultsOfBatchCaseInstanceMigration(String migrationBatchId) { diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationBuilderImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationBuilderImpl.java new file mode 100644 index 00000000000..a6055348b44 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationBuilderImpl.java @@ -0,0 +1,101 @@ +/* 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.engine.impl.migration; + +import org.flowable.batch.api.Batch; +import org.flowable.cmmn.api.CmmnMigrationService; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationBuilder; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.common.engine.api.FlowableException; + +public class HistoricCaseInstanceMigrationBuilderImpl implements HistoricCaseInstanceMigrationBuilder { + + protected CmmnMigrationService cmmnMigrationService; + protected HistoricCaseInstanceMigrationDocumentBuilderImpl historicCaseInstanceMigrationDocumentDocumentBuilder = new HistoricCaseInstanceMigrationDocumentBuilderImpl(); + + public HistoricCaseInstanceMigrationBuilderImpl(CmmnMigrationService cmmnMigrationService) { + this.cmmnMigrationService = cmmnMigrationService; + } + + @Override + public HistoricCaseInstanceMigrationBuilder fromHistoricCaseInstanceMigrationDocument(HistoricCaseInstanceMigrationDocument caseInstanceMigrationDocument) { + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setCaseDefinitionToMigrateTo(caseInstanceMigrationDocument.getMigrateToCaseDefinitionId()); + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setCaseDefinitionToMigrateTo(caseInstanceMigrationDocument.getMigrateToCaseDefinitionKey(), caseInstanceMigrationDocument.getMigrateToCaseDefinitionVersion()); + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setTenantId(caseInstanceMigrationDocument.getMigrateToCaseDefinitionTenantId()); + return this; + } + + @Override + public HistoricCaseInstanceMigrationBuilder migrateToCaseDefinition(String caseDefinitionId) { + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setCaseDefinitionToMigrateTo(caseDefinitionId); + return this; + } + + @Override + public HistoricCaseInstanceMigrationBuilder migrateToCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion) { + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setCaseDefinitionToMigrateTo(caseDefinitionKey, caseDefinitionVersion); + return this; + } + + @Override + public HistoricCaseInstanceMigrationBuilder migrateToCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId) { + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setCaseDefinitionToMigrateTo(caseDefinitionKey, caseDefinitionVersion); + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setTenantId(caseDefinitionTenantId); + return this; + } + + @Override + public HistoricCaseInstanceMigrationBuilder withMigrateToCaseDefinitionTenantId(String caseDefinitionTenantId) { + this.historicCaseInstanceMigrationDocumentDocumentBuilder.setTenantId(caseDefinitionTenantId); + return this; + } + + @Override + public HistoricCaseInstanceMigrationDocument getHistoricCaseInstanceMigrationDocument() { + return this.historicCaseInstanceMigrationDocumentDocumentBuilder.build(); + } + + @Override + public void migrate(String caseInstanceId) { + getCmmnMigrationService().migrateHistoricCaseInstance(caseInstanceId, getHistoricCaseInstanceMigrationDocument()); + } + + @Override + public void migrateHistoricCaseInstances(String caseDefinitionId) { + getCmmnMigrationService().migrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionId, getHistoricCaseInstanceMigrationDocument()); + } + + @Override + public Batch batchMigrateHistoricCaseInstances(String caseDefinitionId) { + return getCmmnMigrationService().batchMigrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionId, getHistoricCaseInstanceMigrationDocument()); + } + + @Override + public void migrateHistoricCaseInstances(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId) { + getCmmnMigrationService().migrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, getHistoricCaseInstanceMigrationDocument()); + } + + @Override + public Batch batchMigrateHistoricCaseInstances(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId) { + return getCmmnMigrationService().batchMigrateHistoricCaseInstancesOfCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, getHistoricCaseInstanceMigrationDocument()); + } + + protected CmmnMigrationService getCmmnMigrationService() { + if (cmmnMigrationService == null) { + throw new FlowableException("CaseMigrationService cannot be null, Obtain your builder instance from the CaseMigrationService to access this feature"); + } + return cmmnMigrationService; + } + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentBuilderImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentBuilderImpl.java new file mode 100644 index 00000000000..89a12e1904e --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentBuilderImpl.java @@ -0,0 +1,52 @@ +/* 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.engine.impl.migration; + +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocumentBuilder; + +public class HistoricCaseInstanceMigrationDocumentBuilderImpl implements HistoricCaseInstanceMigrationDocumentBuilder { + + protected String migrateToCaseDefinitionId; + protected String migrateToCaseDefinitionKey; + protected Integer migrateToCaseDefinitionVersion; + protected String migrateToCaseDefinitionTenantId; + + @Override + public HistoricCaseInstanceMigrationDocumentBuilder setCaseDefinitionToMigrateTo(String caseDefinitionId) { + this.migrateToCaseDefinitionId = caseDefinitionId; + return this; + } + + @Override + public HistoricCaseInstanceMigrationDocumentBuilder setCaseDefinitionToMigrateTo(String caseDefinitionKey, Integer caseDefinitionVersion) { + this.migrateToCaseDefinitionKey = caseDefinitionKey; + this.migrateToCaseDefinitionVersion = caseDefinitionVersion; + return this; + } + + @Override + public HistoricCaseInstanceMigrationDocumentBuilder setTenantId(String caseDefinitionTenantId) { + this.migrateToCaseDefinitionTenantId = caseDefinitionTenantId; + return this; + } + + @Override + public HistoricCaseInstanceMigrationDocument build() { + HistoricCaseInstanceMigrationDocumentImpl caseInstanceMigrationDocument = new HistoricCaseInstanceMigrationDocumentImpl(); + caseInstanceMigrationDocument.setMigrateToCaseDefinitionId(this.migrateToCaseDefinitionId); + caseInstanceMigrationDocument.setMigrateToCaseDefinition(this.migrateToCaseDefinitionKey, this.migrateToCaseDefinitionVersion, this.migrateToCaseDefinitionTenantId); + return caseInstanceMigrationDocument; + } +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentConverter.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentConverter.java new file mode 100644 index 00000000000..bbbd360303b --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentConverter.java @@ -0,0 +1,100 @@ +/* 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.engine.impl.migration; + +import java.io.IOException; + +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; +import org.flowable.common.engine.api.FlowableException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class HistoricCaseInstanceMigrationDocumentConverter implements CaseInstanceMigrationDocumentConstants { + + protected static ObjectMapper objectMapper = new ObjectMapper(); + + public static JsonNode convertToJson(HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + ObjectNode documentNode = objectMapper.createObjectNode(); + + if (historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionId() != null) { + documentNode.put(TO_CASE_DEFINITION_ID_JSON_PROPERTY, historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionId()); + } + + if (historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionKey() != null) { + documentNode.put(TO_CASE_DEFINITION_KEY_JSON_PROPERTY, historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionKey()); + } + + if (historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionVersion() != null) { + documentNode.put(TO_CASE_DEFINITION_VERSION_JSON_PROPERTY, historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionVersion()); + } + + if (historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionTenantId() != null) { + documentNode.put(TO_CASE_DEFINITION_TENANT_ID_JSON_PROPERTY, historicCaseInstanceMigrationDocument.getMigrateToCaseDefinitionTenantId()); + } + + return documentNode; + } + + public static String convertToJsonString(HistoricCaseInstanceMigrationDocument historicCaseInstanceMigrationDocument) { + JsonNode jsonNode = convertToJson(historicCaseInstanceMigrationDocument); + ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); + try { + return objectWriter.writeValueAsString(jsonNode); + } catch (JsonProcessingException e) { + return jsonNode.toString(); + } + } + + public static HistoricCaseInstanceMigrationDocument convertFromJson(String jsonCaseInstanceMigrationDocument) { + + try { + JsonNode rootNode = objectMapper.readTree(jsonCaseInstanceMigrationDocument); + HistoricCaseInstanceMigrationDocumentBuilderImpl documentBuilder = new HistoricCaseInstanceMigrationDocumentBuilderImpl(); + + documentBuilder.setCaseDefinitionToMigrateTo(getJsonProperty(TO_CASE_DEFINITION_ID_JSON_PROPERTY, rootNode)); + + String caseDefinitionKey = getJsonProperty(TO_CASE_DEFINITION_KEY_JSON_PROPERTY, rootNode); + Integer caseDefinitionVersion = getJsonPropertyAsInteger(TO_CASE_DEFINITION_VERSION_JSON_PROPERTY, rootNode); + documentBuilder.setCaseDefinitionToMigrateTo(caseDefinitionKey, caseDefinitionVersion); + + documentBuilder.setTenantId(getJsonProperty(TO_CASE_DEFINITION_TENANT_ID_JSON_PROPERTY, rootNode)); + + return documentBuilder.build(); + + } catch (IOException e) { + throw new FlowableException("Error parsing Historic Case Instance Migration Document", e); + } + + } + + protected static String getJsonProperty(String propertyName, JsonNode jsonNode) { + if (jsonNode.has(propertyName) && !jsonNode.get(propertyName).isNull()) { + return jsonNode.get(propertyName).asText(); + } + + return null; + } + + protected static Integer getJsonPropertyAsInteger(String propertyName, JsonNode jsonNode) { + if (jsonNode.has(propertyName) && !jsonNode.get(propertyName).isNull()) { + return jsonNode.get(propertyName).asInt(); + } + + return null; + } +} + diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentImpl.java new file mode 100644 index 00000000000..0a5cb71c102 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/HistoricCaseInstanceMigrationDocumentImpl.java @@ -0,0 +1,68 @@ +/* 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.engine.impl.migration; + +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; + +public class HistoricCaseInstanceMigrationDocumentImpl implements HistoricCaseInstanceMigrationDocument { + + protected String migrateToCaseDefinitionId; + protected String migrateToCaseDefinitionKey; + protected Integer migrateToCaseDefinitionVersion; + protected String migrateToCaseDefinitionTenantId; + + public static HistoricCaseInstanceMigrationDocument fromJson(String caseInstanceMigrationDocumentJson) { + return HistoricCaseInstanceMigrationDocumentConverter.convertFromJson(caseInstanceMigrationDocumentJson); + } + + public void setMigrateToCaseDefinitionId(String caseDefinitionId) { + this.migrateToCaseDefinitionId = caseDefinitionId; + } + + public void setMigrateToCaseDefinition(String caseDefinitionKey, Integer caseDefinitionVersion) { + this.migrateToCaseDefinitionKey = caseDefinitionKey; + this.migrateToCaseDefinitionVersion = caseDefinitionVersion; + } + + public void setMigrateToCaseDefinition(String caseDefinitionKey, Integer caseDefinitionVersion, String caseDefinitionTenantId) { + this.migrateToCaseDefinitionKey = caseDefinitionKey; + this.migrateToCaseDefinitionVersion = caseDefinitionVersion; + this.migrateToCaseDefinitionTenantId = caseDefinitionTenantId; + } + + @Override + public String getMigrateToCaseDefinitionId() { + return this.migrateToCaseDefinitionId; + } + + @Override + public String getMigrateToCaseDefinitionKey() { + return this.migrateToCaseDefinitionKey; + } + + @Override + public Integer getMigrateToCaseDefinitionVersion() { + return this.migrateToCaseDefinitionVersion; + } + + @Override + public String getMigrateToCaseDefinitionTenantId() { + return this.migrateToCaseDefinitionTenantId; + } + + @Override + public String asJsonString() { + return HistoricCaseInstanceMigrationDocumentConverter.convertToJsonString(this); + } +} diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationBatchTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationBatchTest.java index 4172d2d4ec7..8ae7dbefec8 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationBatchTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationBatchTest.java @@ -90,7 +90,7 @@ void testCaseInstanceBatchMigrationSuccess() { .caseInstanceId(caseInstance1.getId()) .singleResult(); CaseInstance caseInstance2AfterMigration = cmmnRuntimeService.createCaseInstanceQuery() - .caseInstanceId(caseInstance1.getId()) + .caseInstanceId(caseInstance2.getId()) .singleResult(); for (CaseInstanceBatchMigrationPartResult part : migrationResult.getAllMigrationParts()) { @@ -160,7 +160,7 @@ void testCaseInstanceBatchMigrationWithError() { .caseInstanceId(caseInstance1.getId()) .singleResult(); CaseInstance caseInstance2AfterMigration = cmmnRuntimeService.createCaseInstanceQuery() - .caseInstanceId(caseInstance1.getId()) + .caseInstanceId(caseInstance2.getId()) .singleResult(); executeMigrationJobStatusHandlerTimerJob(); diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationBatchTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationBatchTest.java new file mode 100644 index 00000000000..d92d9d5fde9 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationBatchTest.java @@ -0,0 +1,248 @@ +/* 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.Comparator; +import java.util.List; + +import org.flowable.batch.api.Batch; +import org.flowable.cmmn.api.history.HistoricCaseInstance; +import org.flowable.cmmn.api.history.HistoricPlanItemInstance; +import org.flowable.cmmn.api.migration.CaseInstanceBatchMigrationPartResult; +import org.flowable.cmmn.api.migration.CaseInstanceBatchMigrationResult; +import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocumentBuilder; +import org.flowable.cmmn.api.repository.CaseDefinition; +import org.flowable.cmmn.api.repository.CmmnDeployment; +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.engine.impl.job.CaseInstanceMigrationStatusJobHandler; +import org.flowable.cmmn.engine.impl.migration.HistoricCaseInstanceMigrationDocumentBuilderImpl; +import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; +import org.flowable.cmmn.engine.test.impl.CmmnJobTestHelper; +import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.job.api.Job; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.junit.jupiter.api.Test; + +public class HistoricCaseInstanceMigrationBatchTest extends AbstractCaseMigrationTest { + + @Test + void testHistoricCaseInstanceBatchMigrationSuccess() { + // GIVEN + CaseDefinition sourceCaseDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/one-task.cmmn.xml"); + CaseInstance caseInstance1 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance3 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml"); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance1.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance2.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + HistoricCaseInstanceMigrationDocumentBuilder migrationDoc = new HistoricCaseInstanceMigrationDocumentBuilderImpl() + .setCaseDefinitionToMigrateTo(destinationDefinition.getId()); + + Batch batch = cmmnMigrationService.createHistoricCaseInstanceMigrationBuilderFromHistoricCaseInstanceMigrationDocument(migrationDoc.build()) + .batchMigrateHistoricCaseInstances(sourceCaseDefinition.getId()); + + assertThat(CmmnJobTestHelper.areJobsAvailable(cmmnManagementService)).isTrue(); + + CaseInstanceBatchMigrationResult migrationResultPriorProcessing = cmmnMigrationService.getResultsOfBatchCaseInstanceMigration(batch.getId()); + + // assert created migration result and parts + assertThat(migrationResultPriorProcessing).isNotNull(); + assertThat(migrationResultPriorProcessing.getBatchId()).isEqualTo(batch.getId()); + assertThat(migrationResultPriorProcessing.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_IN_PROGRESS); + assertThat(migrationResultPriorProcessing.getCompleteTime()).isNull(); + assertThat(migrationResultPriorProcessing.getAllMigrationParts()).hasSize(2); + assertThat(migrationResultPriorProcessing.getWaitingMigrationParts()).hasSize(2); + assertThat(migrationResultPriorProcessing.getSuccessfulMigrationParts()).isEmpty(); + assertThat(migrationResultPriorProcessing.getFailedMigrationParts()).isEmpty(); + + for (CaseInstanceBatchMigrationPartResult part : migrationResultPriorProcessing.getAllMigrationParts()) { + assertThat(part.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_WAITING); + assertThat(part.getResult()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_WAITING); + } + + // WHEN + // Start async executor to process the batches + CmmnJobTestHelper.waitForJobExecutorToProcessAllAsyncJobs(cmmnEngineConfiguration, 5000L, 500L, true); + assertThat(CmmnJobTestHelper.areJobsAvailable(cmmnManagementService)).isFalse(); + executeMigrationJobStatusHandlerTimerJob(); + + // THEN + CaseInstanceBatchMigrationResult migrationResult = cmmnMigrationService.getResultsOfBatchCaseInstanceMigration(batch.getId()); + assertThat(migrationResult).isNotNull(); + assertThat(migrationResult.getBatchId()).isEqualTo(batch.getId()); + assertThat(migrationResult.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_COMPLETED); + + HistoricCaseInstance caseInstance1AfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance1.getId()) + .singleResult(); + HistoricCaseInstance caseInstance2AfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance2.getId()) + .singleResult(); + + for (CaseInstanceBatchMigrationPartResult part : migrationResult.getAllMigrationParts()) { + assertThat(part.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_COMPLETED); + assertThat(part.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_COMPLETED); + assertThat(part.getResult()).isEqualTo(CaseInstanceBatchMigrationResult.RESULT_SUCCESS); + assertThat(part.getResult()).isEqualTo(CaseInstanceBatchMigrationResult.RESULT_SUCCESS); + } + + assertAfterMigrationState(1, caseInstance1, destinationDefinition, caseInstance1AfterMigration, 2); + assertAfterMigrationState(1, caseInstance2, destinationDefinition, caseInstance2AfterMigration, 2); + + HistoricCaseInstance caseInstance3AfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance3.getId()) + .singleResult(); + assertThat(caseInstance3AfterMigration.getCaseDefinitionId()).isEqualTo(sourceCaseDefinition.getId()); + + cmmnManagementService.deleteBatch(batch.getId()); + } + + @Test + void testHistoricCaseInstanceBatchMigrationWithError() { + // GIVEN + CaseDefinition caseDefinitionVersion1 = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml"); + CaseInstance caseInstance1 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance3 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + + List tasks = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance1.getId()).list(); + for (Task task : tasks) { + cmmnTaskService.complete(task.getId()); + } + + tasks = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance2.getId()).list(); + for (Task task : tasks) { + cmmnTaskService.complete(task.getId()); + } + + CmmnDeployment deployment = cmmnRepositoryService.createDeployment() + .name("test1") + // Other tenant, migration throws an exception. + .tenantId("otherTenant") + .addClasspathResource("org/flowable/cmmn/test/migration/stage-linked-with-sentry.cmmn.xml") + .deploy(); + + CaseDefinition caseDefinitionVersion2 = cmmnRepositoryService.createCaseDefinitionQuery() + .deploymentId(deployment.getId()) + .singleResult(); + + List caseDefinitions = cmmnRepositoryService.createCaseDefinitionQuery() + .caseDefinitionKey("testCase") + .list(); + + assertThat(caseDefinitions).hasSize(2); + caseDefinitions.sort(Comparator.comparingInt(CaseDefinition::getVersion)); + + assertThat(caseDefinitionVersion1.getId()).isEqualTo(caseDefinitions.get(0).getId()); + assertThat(caseDefinitionVersion2.getId()).isEqualTo(caseDefinitions.get(1).getId()); + + HistoricCaseInstanceMigrationDocumentBuilder migrationDoc = new HistoricCaseInstanceMigrationDocumentBuilderImpl() + .setCaseDefinitionToMigrateTo(caseDefinitionVersion2.getId()); + + Batch batch = cmmnMigrationService.createHistoricCaseInstanceMigrationBuilderFromHistoricCaseInstanceMigrationDocument(migrationDoc.build()) + .batchMigrateHistoricCaseInstances(caseDefinitionVersion1.getId()); + + // assert created migration result and parts + assertThat(CmmnJobTestHelper.areJobsAvailable(cmmnManagementService)).isTrue(); + CaseInstanceBatchMigrationResult migrationResultPriorProcessing = cmmnMigrationService.getResultsOfBatchCaseInstanceMigration(batch.getId()); + assertThat(migrationResultPriorProcessing).isNotNull(); + assertThat(migrationResultPriorProcessing.getBatchId()).isEqualTo(batch.getId()); + assertThat(migrationResultPriorProcessing.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_IN_PROGRESS); + assertThat(migrationResultPriorProcessing.getCompleteTime()).isNull(); + assertThat(migrationResultPriorProcessing.getAllMigrationParts()).hasSize(2); + assertThat(migrationResultPriorProcessing.getWaitingMigrationParts()).hasSize(2); + assertThat(migrationResultPriorProcessing.getSuccessfulMigrationParts()).isEmpty(); + assertThat(migrationResultPriorProcessing.getFailedMigrationParts()).isEmpty(); + + // WHEN + // Start async executor to process the batches + CmmnJobTestHelper.waitForJobExecutorToProcessAllAsyncJobs(cmmnEngineConfiguration, 5000L, 500L, true); + assertThat(CmmnJobTestHelper.areJobsAvailable(cmmnManagementService)).isFalse(); + + HistoricCaseInstance caseInstance1AfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance1.getId()) + .singleResult(); + HistoricCaseInstance caseInstance2AfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance2.getId()) + .singleResult(); + + executeMigrationJobStatusHandlerTimerJob(); + + // THEN + CaseInstanceBatchMigrationResult migrationResult = cmmnMigrationService.getResultsOfBatchCaseInstanceMigration(batch.getId()); + assertThat(migrationResult.getBatchId()).isEqualTo(batch.getId()); + assertThat(migrationResult.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_COMPLETED); + assertThat(migrationResult.getCompleteTime()).isNotNull(); + assertThat(migrationResult.getAllMigrationParts()).hasSize(2); + assertThat(migrationResult.getWaitingMigrationParts()).isEmpty(); + assertThat(migrationResult.getSuccessfulMigrationParts()).hasSize(0); + assertThat(migrationResult.getFailedMigrationParts()).hasSize(2); + + for (CaseInstanceBatchMigrationPartResult part : migrationResult.getAllMigrationParts()) { + assertThat(part.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_COMPLETED); + assertThat(part.getStatus()).isEqualTo(CaseInstanceBatchMigrationResult.STATUS_COMPLETED); + assertThat(part.getResult()).isEqualTo(CaseInstanceBatchMigrationResult.RESULT_FAIL); + assertThat(part.getResult()).isEqualTo(CaseInstanceBatchMigrationResult.RESULT_FAIL); + } + + assertAfterMigrationState(2, caseInstance1, caseDefinitionVersion1, caseInstance1AfterMigration, 1); + assertAfterMigrationState(2, caseInstance2, caseDefinitionVersion1, caseInstance2AfterMigration, 1); + + cmmnManagementService.deleteBatch(batch.getId()); + } + + void assertAfterMigrationState(int numberOfPlanItems, CaseInstance caseInstance, CaseDefinition destinationDefinition, + HistoricCaseInstance caseInstanceAfterMigration, int caseDefinitionVersion) { + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + assertThat(caseInstanceAfterMigration.getCaseDefinitionKey()).isEqualTo("testCase"); + assertThat(caseInstanceAfterMigration.getCaseDefinitionName()).isEqualTo("Two Task Test Case"); + assertThat(caseInstanceAfterMigration.getCaseDefinitionVersion()).isEqualTo(caseDefinitionVersion); + assertThat(caseInstanceAfterMigration.getCaseDefinitionDeploymentId()).isEqualTo(destinationDefinition.getDeploymentId()); + + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId()) + .isEqualTo(destinationDefinition.getId()); + + List historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery() + .planItemInstanceCaseInstanceId(caseInstance.getId()).list(); + assertThat(historicPlanItemInstances).hasSize(numberOfPlanItems); + for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) { + assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + + List historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(historicTasks).hasSize(numberOfPlanItems); + for (HistoricTaskInstance historicTask : historicTasks) { + assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + } + } + + protected void executeMigrationJobStatusHandlerTimerJob() { + List timerJobs = cmmnManagementService.createTimerJobQuery().handlerType(CaseInstanceMigrationStatusJobHandler.TYPE).list(); + for (Job timerJob : timerJobs) { + Job executableJob = cmmnManagementService.moveTimerToExecutableJob(timerJob.getId()); + cmmnManagementService.executeJob(executableJob.getId()); + } + } +} diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationTest.java new file mode 100644 index 00000000000..ebc8455d7d7 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/HistoricCaseInstanceMigrationTest.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 static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; + +import org.flowable.cmmn.api.history.HistoricCaseInstance; +import org.flowable.cmmn.api.history.HistoricPlanItemInstance; +import org.flowable.cmmn.api.repository.CaseDefinition; +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.api.runtime.PlanItemInstanceState; +import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.junit.jupiter.api.Test; + +public class HistoricCaseInstanceMigrationTest extends AbstractCaseMigrationTest { + + @Test + void withSimpleOneTaskCase() { + // Arrange + deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/one-task.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml"); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + // Act + cmmnMigrationService.createHistoricCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrate(caseInstance.getId()); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + // Assert + HistoricCaseInstance historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + + assertHistoricCaseInstanceValues(historicCaseInstanceAfterMigration, caseInstance, destinationDefinition); + } + } + + @Test + void withRunningOneTaskCase() { + // Arrange + deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/one-task.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml"); + + // Act + assertThatThrownBy(() -> { + cmmnMigrationService.createHistoricCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrate(caseInstance.getId()); + }).isInstanceOf(FlowableException.class).hasMessageContaining("Historic case instance has not ended"); + } + + @Test + void migrateMultipleOneTaskCasesWithCaseDefinitionId() { + // Arrange + CaseDefinition originalCaseDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/one-task.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance3 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml"); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance2.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + // Act + cmmnMigrationService.createHistoricCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrateHistoricCaseInstances(originalCaseDefinition.getId()); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + // Assert + HistoricCaseInstance historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + + assertHistoricCaseInstanceValues(historicCaseInstanceAfterMigration, caseInstance, destinationDefinition); + + historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance2.getId()) + .singleResult(); + + assertHistoricCaseInstanceValues(historicCaseInstanceAfterMigration, caseInstance2, destinationDefinition); + + historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance3.getId()) + .singleResult(); + + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(originalCaseDefinition.getId()); + } + } + + @Test + void migrateMultipleOneTaskCasesWithCaseDefinitionKey() { + // Arrange + CaseDefinition originalCaseDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/one-task.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseInstance caseInstance3 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/two-task.cmmn.xml"); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance2.getId()).singleResult(); + cmmnTaskService.complete(task.getId()); + + // Act + cmmnMigrationService.createHistoricCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .migrateHistoricCaseInstances(originalCaseDefinition.getKey(), 1, ""); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + // Assert + HistoricCaseInstance historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + + assertHistoricCaseInstanceValues(historicCaseInstanceAfterMigration, caseInstance, destinationDefinition); + + historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance2.getId()) + .singleResult(); + + assertHistoricCaseInstanceValues(historicCaseInstanceAfterMigration, caseInstance2, destinationDefinition); + + historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(caseInstance3.getId()) + .singleResult(); + + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(originalCaseDefinition.getId()); + } + } + + protected void assertHistoricCaseInstanceValues(HistoricCaseInstance historicCaseInstanceAfterMigration, CaseInstance caseInstance, CaseDefinition destinationDefinition) { + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionKey()).isEqualTo("testCase"); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionName()).isEqualTo("Two Task Test Case"); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionVersion()).isEqualTo(2); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionDeploymentId()).isEqualTo(destinationDefinition.getDeploymentId()); + List planItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery() + .planItemInstanceCaseInstanceId(caseInstance.getId()) + .list(); + assertThat(planItemInstances).hasSize(1); + assertThat(planItemInstances) + .extracting(HistoricPlanItemInstance::getCaseDefinitionId) + .containsOnly(destinationDefinition.getId()); + assertThat(planItemInstances) + .extracting(HistoricPlanItemInstance::getName) + .containsExactlyInAnyOrder("Task 1"); + assertThat(planItemInstances) + .extracting(HistoricPlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.COMPLETED); + + List historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(historicTasks).hasSize(1); + for (HistoricTaskInstance historicTask : historicTasks) { + assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + } + +} diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/reactivation/SimpleHistoricCaseReactivationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/reactivation/SimpleHistoricCaseReactivationTest.java new file mode 100644 index 00000000000..d87a741713f --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/reactivation/SimpleHistoricCaseReactivationTest.java @@ -0,0 +1,166 @@ +/* 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.reactivation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.flowable.cmmn.api.runtime.PlanItemInstanceState.ACTIVE; +import static org.flowable.cmmn.api.runtime.PlanItemInstanceState.COMPLETED; +import static org.flowable.cmmn.api.runtime.PlanItemInstanceState.TERMINATED; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.flowable.cmmn.api.history.HistoricCaseInstance; +import org.flowable.cmmn.api.history.HistoricPlanItemInstance; +import org.flowable.cmmn.api.repository.CaseDefinition; +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.api.runtime.PlanItemInstance; +import org.flowable.cmmn.engine.test.CmmnDeployment; +import org.flowable.cmmn.engine.test.FlowableCmmnTestCase; +import org.flowable.cmmn.engine.test.impl.CmmnTestHelper; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.variable.api.history.HistoricVariableInstance; +import org.flowable.variable.api.persistence.entity.VariableInstance; +import org.junit.Test; + +public class SimpleHistoricCaseReactivationTest extends FlowableCmmnTestCase { + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/reactivation/Simple_Reactivation_Test_Case.cmmn.xml") + public void simpleMigrateCaseReactivationTest() { + String previousUserId = Authentication.getAuthenticatedUserId(); + org.flowable.cmmn.api.repository.CmmnDeployment deployment = null; + try { + Authentication.setAuthenticatedUserId("simpleCaseReactivationTest_user"); + final HistoricCaseInstance historicCase = createAndFinishSimpleCase("simpleReactivationTestCase"); + + deployment = cmmnRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/reactivation/Simple_Migrate_Reactivation_Test_Case.cmmn.xml") + .deploy(); + + CaseDefinition newCaseDefinition = cmmnRepositoryService.createCaseDefinitionQuery() + .deploymentId(deployment.getId()) + .singleResult(); + + cmmnMigrationService.createHistoricCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(newCaseDefinition.getId()) + .migrate(historicCase.getId()); + + HistoricCaseInstance historicCaseInstanceAfterMigration = cmmnHistoryService.createHistoricCaseInstanceQuery() + .caseInstanceId(historicCase.getId()) + .singleResult(); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(newCaseDefinition.getId()); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionKey()).isEqualTo("simpleReactivationTestCase"); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionName()).isEqualTo("Simple Migrate Reactivation Test Case"); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionVersion()).isEqualTo(2); + assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionDeploymentId()).isEqualTo(newCaseDefinition.getDeploymentId()); + + CaseInstance reactivatedCase = cmmnHistoryService.createCaseReactivationBuilder(historicCase.getId()).reactivate(); + assertThat(reactivatedCase).isNotNull(); + + List planItemInstances = getAllPlanItemInstances(reactivatedCase.getId()); + assertThat(planItemInstances).isNotNull().hasSize(8); + + // we need to have two reactivation listeners by now, one in terminated state (from the first case completion) and the second one needs to be + // in completion state as we just triggered it for case reactivation + assertPlanItemInstanceState(planItemInstances, "Reactivate case", TERMINATED, COMPLETED); + + assertPlanItemInstanceState(planItemInstances, "Task C", TERMINATED); + + // the same for the task D, one instance needs to be active + assertPlanItemInstanceState(planItemInstances, "Task D", ACTIVE); + assertCaseInstanceNotEnded(reactivatedCase); + + // the plan items must be equal for both the runtime as well as the history as of now + assertSamePlanItemState(reactivatedCase); + + // make sure we have exactly the same variables as the historic case + assertSameVariables(historicCase, reactivatedCase); + + List historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery().list(); + assertThat(historicPlanItemInstances).isNotNull().hasSize(8); + + Map historicPlanItemInstanceMap = new HashMap<>(); + for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) { + historicPlanItemInstanceMap.put(historicPlanItemInstance.getId(), historicPlanItemInstance); + } + + for (PlanItemInstance planItemInstance : planItemInstances) { + assertThat(historicPlanItemInstanceMap.containsKey(planItemInstance.getId())).isTrue(); + HistoricPlanItemInstance historicPlanItemInstance = historicPlanItemInstanceMap.get(planItemInstance.getId()); + assertThat(historicPlanItemInstance.getState()).isEqualTo(planItemInstance.getState()); + assertThat(historicPlanItemInstance.getElementId()).isEqualTo(planItemInstance.getElementId()); + } + + } finally { + if (deployment != null) { + CmmnTestHelper.deleteDeployment(cmmnEngineConfiguration, deployment.getId()); + } + Authentication.setAuthenticatedUserId(previousUserId); + } + } + + protected HistoricCaseInstance createAndFinishSimpleCase(String caseDefinitionKey) { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey(caseDefinitionKey) + .variable("foo", "fooValue") + .variable("bar", "barValue") + .start(); + + List planItemInstances = getPlanItemInstances(caseInstance.getId()); + assertPlanItemInstanceState(planItemInstances, "Task A", ACTIVE); + cmmnRuntimeService.triggerPlanItemInstance(getPlanItemInstanceIdByName(planItemInstances, "Task A")); + + planItemInstances = getPlanItemInstances(caseInstance.getId()); + assertPlanItemInstanceState(planItemInstances, "Task B", ACTIVE); + cmmnRuntimeService.triggerPlanItemInstance(getPlanItemInstanceIdByName(planItemInstances, "Task B")); + + return cmmnHistoryService.createHistoricCaseInstanceQuery().finished().singleResult(); + } + + protected void assertSameVariables(HistoricCaseInstance c1, CaseInstance c2) { + List originalVars = cmmnEngineConfiguration.getCmmnHistoryService().createHistoricVariableInstanceQuery() + .caseInstanceId(c1.getId()) + .list(); + + Map reactivatedVars = cmmnEngineConfiguration.getCmmnRuntimeService().getVariableInstances(c2.getId()); + + for (HistoricVariableInstance originalVar : originalVars) { + VariableInstance reactivatedVar = reactivatedVars.remove(originalVar.getVariableName()); + assertThat(reactivatedVar).isNotNull(); + assertThat(reactivatedVar.getValue()).isEqualTo(originalVar.getValue()); + } + + assertThat(reactivatedVars).hasSize(0); + } + + protected void assertVariableValue(Map variables, String name, Object value) { + VariableInstance variable = variables.get(name); + assertThat(variable).isNotNull(); + assertThat(variable.getValue()).isNotNull().isEqualTo(value); + } + + protected void assertHistoricVariableValue(List variables, String name, Object value) { + for (HistoricVariableInstance variable : variables) { + if (variable.getVariableName().equals(name)) { + assertThat(variable.getValue()).isEqualTo(value); + } + } + } + + protected void assertHistoricVariableNotExisting(List variables, String name) { + assertThat(variables.stream().filter(v -> v.getVariableName().equals(name)).collect(Collectors.toList())).isEmpty(); + } +} diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/reactivation/Simple_Migrate_Reactivation_Test_Case.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/reactivation/Simple_Migrate_Reactivation_Test_Case.cmmn.xml new file mode 100644 index 00000000000..89e2be11811 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/reactivation/Simple_Migrate_Reactivation_Test_Case.cmmn.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + complete + + + + + occur + + + + + + + + + + + + + + + + + + + \ No newline at end of file