From f502b071acc60eab463f27d1fa01e522ffc68cf8 Mon Sep 17 00:00:00 2001 From: Valentin Zickner <3200232+vzickner@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:07:30 +0200 Subject: [PATCH 1/3] allow to move already available plan items to available without failing (#3872) --- .../AbstractCmmnDynamicStateManager.java | 9 ++++++ .../migration/CaseInstanceMigrationTest.java | 29 +++++++++++++++++++ .../migration/task-followed-by-stage.cmmn.xml | 24 +++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-followed-by-stage.cmmn.xml diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java index 027a87f0eb0..91883f864ca 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java @@ -355,6 +355,7 @@ protected void executeChangePlanItemInstancesToAvailableState(CaseInstanceChange } PlanItemInstance existingPlanItemInstance = null; + boolean allExistingPlanItemsAreAvailable = true; for (PlanItemInstance planItemInstance : planItemInstances) { if (PlanItemInstanceState.ACTIVE.equals(planItemInstance.getState()) || PlanItemInstanceState.ENABLED.equals(planItemInstance.getState())) { if (existingPlanItemInstance != null) { @@ -363,6 +364,14 @@ protected void executeChangePlanItemInstancesToAvailableState(CaseInstanceChange existingPlanItemInstance = planItemInstance; } } + if (!PlanItemInstanceState.AVAILABLE.equals(planItemInstance.getState())) { + allExistingPlanItemsAreAvailable = false; + } + } + + if (allExistingPlanItemsAreAvailable) { + // all existing plan items are available, we can continue without any changes + continue; } if (existingPlanItemInstance == null) { diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java index 6e09b3539fd..63142feb604 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import org.assertj.core.groups.Tuple; import org.flowable.cmmn.api.history.HistoricMilestoneInstance; import org.flowable.cmmn.api.history.HistoricPlanItemInstance; import org.flowable.cmmn.api.migration.ActivatePlanItemDefinitionMapping; @@ -5252,6 +5253,34 @@ void migrateCaseInstancesWithStageAvailableBasedOnStageCondition() { .containsOnly(PlanItemInstanceState.AVAILABLE); } + @Test + void migrateCaseInstancesWithMoveStageToAvailableAndAlreadyAvailableStage() { + // Arrange + CaseDefinition definition1 = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/task-followed-by-stage.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testCase").start(); + CaseDefinition definition2 = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/task-followed-by-stage.cmmn.xml"); + + assertThat(definition2.getId()).isNotEqualTo(definition1.getId()); + + // Act + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(definition2.getId()) + .addMoveToAvailablePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createMoveToAvailablePlanItemDefinitionMappingFor("cmmnStage_2")) + .migrate(caseInstance.getId()); + + // Assert + List planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .list(); + assertThat(planItemInstances) + .hasSize(2) + .extracting(PlanItemInstance::getName, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + Tuple.tuple("Task 1", PlanItemInstanceState.ACTIVE), + Tuple.tuple("Stage", PlanItemInstanceState.AVAILABLE) + ); + } + @Test void migrateCaseInstancesWithRepetitionAndStageVariable() { // Arrange diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-followed-by-stage.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-followed-by-stage.cmmn.xml new file mode 100644 index 00000000000..f239eac6f3d --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-followed-by-stage.cmmn.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + complete + + + + + + + + + + + + From f10aa6c74d86732f53a65b337c16ba1b47469502 Mon Sep 17 00:00:00 2001 From: Valentin Zickner <3200232+vzickner@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:11:27 +0200 Subject: [PATCH 2/3] add pre- and post upgrade expressions to migration builder (#3871) --- .../CaseInstanceMigrationBuilderImpl.java | 2 ++ .../migration/CaseInstanceMigrationTest.java | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationBuilderImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationBuilderImpl.java index 64356b4245b..ebec190a8eb 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationBuilderImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationBuilderImpl.java @@ -52,6 +52,8 @@ public CaseInstanceMigrationBuilder fromCaseInstanceMigrationDocument(CaseInstan this.caseInstanceMigrationDocumentDocumentBuilder.addWaitingForRepetitionPlanItemDefinitionMappings(caseInstanceMigrationDocument.getWaitingForRepetitionPlanItemDefinitionMappings()); this.caseInstanceMigrationDocumentDocumentBuilder.addRemoveWaitingForRepetitionPlanItemDefinitionMappings(caseInstanceMigrationDocument.getRemoveWaitingForRepetitionPlanItemDefinitionMappings()); this.caseInstanceMigrationDocumentDocumentBuilder.addCaseInstanceVariables(caseInstanceMigrationDocument.getCaseInstanceVariables()); + this.caseInstanceMigrationDocumentDocumentBuilder.preUpgradeExpression(caseInstanceMigrationDocument.getPreUpgradeExpression()); + this.caseInstanceMigrationDocumentDocumentBuilder.postUpgradeExpression(caseInstanceMigrationDocument.getPostUpgradeExpression()); return this; } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java index 63142feb604..bf5839d9101 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java @@ -13,6 +13,7 @@ package org.flowable.cmmn.test.migration; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.flowable.cmmn.converter.CmmnXmlConstants.ELEMENT_STAGE; @@ -27,6 +28,7 @@ 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.CaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.ChangePlanItemIdMapping; import org.flowable.cmmn.api.migration.ChangePlanItemIdWithDefinitionIdMapping; import org.flowable.cmmn.api.migration.PlanItemDefinitionMappingBuilder; @@ -38,6 +40,7 @@ import org.flowable.cmmn.api.runtime.PlanItemInstance; import org.flowable.cmmn.api.runtime.PlanItemInstanceState; import org.flowable.cmmn.api.runtime.UserEventListenerInstance; +import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationDocumentConverter; import org.flowable.cmmn.engine.impl.persistence.entity.SentryPartInstanceEntity; import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; import org.flowable.common.engine.api.FlowableException; @@ -5847,6 +5850,32 @@ void withCaseTaskWhichWillBeTerminated() { assertThat(subcaseInstances).isEqualTo(0); } + @Test + void testCaseInstanceMigrationDocument() { + String documentAsJson = this.cmmnMigrationService.createCaseInstanceMigrationBuilder() + .addTerminatePlanItemDefinitionMapping( + PlanItemDefinitionMappingBuilder.createTerminatePlanItemDefinitionMappingFor("terminateId", "${terminateCondition}")) + .addMoveToAvailablePlanItemDefinitionMapping( + PlanItemDefinitionMappingBuilder.createMoveToAvailablePlanItemDefinitionMappingFor("availableId", "${availableCondition}")) + .addWaitingForRepetitionPlanItemDefinitionMapping( + PlanItemDefinitionMappingBuilder.createWaitingForRepetitionPlanItemDefinitionMappingFor("repetitionId", "${repetitionCondition}")) + .removeWaitingForRepetitionPlanItemDefinitionMapping( + PlanItemDefinitionMappingBuilder.createRemoveWaitingForRepetitionPlanItemDefinitionMappingFor("removeRepetitionId", + "${removeRepetitionCondition}")) + .addActivatePlanItemDefinitionMapping( + PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("activateId", "${activateCondition}")) + .withPreUpgradeExpression("${preExpression}") + .withPostUpgradeExpression("${postExpression}") + .getCaseInstanceMigrationDocument() + .asJsonString(); + CaseInstanceMigrationDocument caseInstanceMigrationDocumentFromString = CaseInstanceMigrationDocumentConverter.convertFromJson(documentAsJson); + String documentAsJsonConverted = this.cmmnMigrationService.createCaseInstanceMigrationBuilderFromCaseInstanceMigrationDocument( + caseInstanceMigrationDocumentFromString) + .getCaseInstanceMigrationDocument() + .asJsonString(); + assertThatJson(documentAsJsonConverted).isEqualTo(documentAsJson); + } + protected class CustomTenantProvider implements DefaultTenantProvider { @Override From feb87b2a1c4194928338e35bbeaf2f82db14faf2 Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Fri, 12 Apr 2024 09:12:04 +0200 Subject: [PATCH 3/3] Fix batch migration transaction + add stacktrace info to failing batch part --- .../CaseInstanceBatchMigrationPartResult.java | 9 ++ ...etCaseInstanceMigrationBatchResultCmd.java | 6 + ...stractCaseInstanceMigrationJobHandler.java | 1 + .../job/CaseInstanceMigrationJobHandler.java | 63 ++++++++--- ...storicCaseInstanceMigrationJobHandler.java | 63 ++++++++--- .../CaseInstanceMigrationManagerImpl.java | 4 +- .../CaseInstanceMigrationBatchTest.java | 103 +++++++++++++++++- ...istoricCaseInstanceMigrationBatchTest.java | 14 ++- .../FlowableBatchPartMigrationException.java | 36 ++++++ ...rocessInstanceMigrationBatchResultCmd.java | 6 + ...actProcessInstanceMigrationJobHandler.java | 1 + .../ProcessInstanceMigrationJobHandler.java | 63 ++++++++--- .../ProcessInstanceMigrationManagerImpl.java | 2 + ...ocessInstanceBatchMigrationPartResult.java | 9 ++ .../ProcessInstanceMigrationBatchTest.java | 45 +++++++- .../impl/AcquiredExternalWorkerJobImpl.java | 1 + .../impl/asyncexecutor/DefaultJobManager.java | 7 ++ .../asyncexecutor/ExecuteAsyncRunnable.java | 20 ++++ .../impl/asyncexecutor/JobManager.java | 2 + 19 files changed, 391 insertions(+), 64 deletions(-) create mode 100644 modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/FlowableBatchPartMigrationException.java diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceBatchMigrationPartResult.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceBatchMigrationPartResult.java index 90928ef8c45..c36223cdfec 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceBatchMigrationPartResult.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/CaseInstanceBatchMigrationPartResult.java @@ -22,6 +22,7 @@ public class CaseInstanceBatchMigrationPartResult { protected String sourceCaseDefinitionId; protected String targetCaseDefinitionId; protected String migrationMessage; + protected String migrationStacktrace; public String getBatchId() { return batchId; @@ -78,4 +79,12 @@ public String getMigrationMessage() { public void setMigrationMessage(String migrationMessage) { this.migrationMessage = migrationMessage; } + + public String getMigrationStacktrace() { + return migrationStacktrace; + } + + public void setMigrationStacktrace(String migrationStacktrace) { + this.migrationStacktrace = migrationStacktrace; + } } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetCaseInstanceMigrationBatchResultCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetCaseInstanceMigrationBatchResultCmd.java index 1b0773a39d4..5300386ee42 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetCaseInstanceMigrationBatchResultCmd.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetCaseInstanceMigrationBatchResultCmd.java @@ -36,6 +36,7 @@ public class GetCaseInstanceMigrationBatchResultCmd implements Command { protected static final String BATCH_RESULT_MESSAGE_LABEL = "resultMessage"; + protected static final String BATCH_RESULT_STACKTRACE_LABEL = "resultStacktrace"; protected String migrationBatchId; public GetCaseInstanceMigrationBatchResultCmd(String migrationBatchId) { @@ -98,6 +99,11 @@ protected CaseInstanceBatchMigrationPartResult convertFromBatchPart(BatchPart ba String resultMessage = resultNode.get(BATCH_RESULT_MESSAGE_LABEL).asText(); partResult.setMigrationMessage(resultMessage); } + + if (resultNode.has(BATCH_RESULT_STACKTRACE_LABEL)) { + String resultStacktrace = resultNode.get(BATCH_RESULT_STACKTRACE_LABEL).asText(); + partResult.setMigrationStacktrace(resultStacktrace); + } } catch (IOException e) { throw new FlowableException("Error reading batch part " + batchPart.getId()); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/AbstractCaseInstanceMigrationJobHandler.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/AbstractCaseInstanceMigrationJobHandler.java index 8ca250a427c..ead3feca77f 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/AbstractCaseInstanceMigrationJobHandler.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/AbstractCaseInstanceMigrationJobHandler.java @@ -25,6 +25,7 @@ public abstract class AbstractCaseInstanceMigrationJobHandler implements JobHand public static final String BATCH_RESULT_STATUS_LABEL = "resultStatus"; public static final String BATCH_RESULT_MESSAGE_LABEL = "resultMessage"; + public static final String BATCH_RESULT_STACKTRACE_LABEL = "resultStacktrace"; protected static final String CFG_LABEL_BATCH_ID = "batchId"; protected static final String CFG_LABEL_BATCH_PART_ID = "batchPartId"; diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CaseInstanceMigrationJobHandler.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CaseInstanceMigrationJobHandler.java index 67896a805a0..c3f27b9ea61 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CaseInstanceMigrationJobHandler.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CaseInstanceMigrationJobHandler.java @@ -12,6 +12,9 @@ */ package org.flowable.cmmn.engine.impl.job; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.flowable.batch.api.Batch; import org.flowable.batch.api.BatchPart; import org.flowable.batch.api.BatchService; @@ -21,7 +24,9 @@ import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationDocumentImpl; import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationManager; import org.flowable.cmmn.engine.impl.util.CommandContextUtil; -import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableBatchPartMigrationException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.variable.api.delegate.VariableScope; @@ -49,31 +54,53 @@ public void execute(JobEntity job, String configuration, VariableScope variableS CaseInstanceMigrationDocument migrationDocument = CaseInstanceMigrationDocumentImpl.fromJson( batch.getBatchDocumentJson(engineConfiguration.getEngineCfgKey())); - String exceptionMessage = null; try { migrationManager.migrateCaseInstance(batchPart.getScopeId(), migrationDocument, commandContext); - } catch (FlowableException e) { - exceptionMessage = e.getMessage(); - } + } catch (Exception e) { + String exceptionMessage = e.getMessage(); + + engineConfiguration.getCommandExecutor().execute(new Command<>() { + @Override + public Void execute(CommandContext commandContext) { + CommandConfig commandConfig = engineConfiguration.getCommandExecutor().getDefaultConfig().transactionRequiresNew(); + return engineConfiguration.getCommandExecutor().execute(commandConfig, new Command<>() { + @Override + public Void execute(CommandContext commandContext2) { + String resultAsJsonString = prepareResultAsJsonString(exceptionMessage, e); + batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); - String resultAsJsonString = prepareResultAsJsonString(exceptionMessage); - - if (exceptionMessage != null) { - batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); - } else { - batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); + return null; + } + }); + } + }); + + FlowableBatchPartMigrationException wrappedException = new FlowableBatchPartMigrationException(e.getMessage(), e); + wrappedException.setIgnoreFailedJob(true); + throw wrappedException; } + + String resultAsJsonString = prepareResultAsJsonString(); + batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); } - protected static String prepareResultAsJsonString(String exceptionMessage) { + protected String prepareResultAsJsonString(String exceptionMessage, Exception e) { 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); - } + objectNode.put(BATCH_RESULT_STATUS_LABEL, CaseInstanceBatchMigrationResult.RESULT_FAIL); + objectNode.put(BATCH_RESULT_MESSAGE_LABEL, exceptionMessage); + objectNode.put(BATCH_RESULT_STACKTRACE_LABEL, getExceptionStacktrace(e)); + return objectNode.toString(); + } + + protected String prepareResultAsJsonString() { + ObjectNode objectNode = getObjectMapper().createObjectNode(); + objectNode.put(BATCH_RESULT_STATUS_LABEL, CaseInstanceBatchMigrationResult.RESULT_SUCCESS); return objectNode.toString(); } + protected String getExceptionStacktrace(Throwable exception) { + StringWriter stringWriter = new StringWriter(); + exception.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } } \ No newline at end of file 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 index aaad65be3c0..0925e930e95 100644 --- 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 @@ -12,6 +12,9 @@ */ package org.flowable.cmmn.engine.impl.job; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.flowable.batch.api.Batch; import org.flowable.batch.api.BatchPart; import org.flowable.batch.api.BatchService; @@ -21,7 +24,9 @@ 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.api.FlowableBatchPartMigrationException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.variable.api.delegate.VariableScope; @@ -49,31 +54,53 @@ public void execute(JobEntity job, String configuration, VariableScope variableS HistoricCaseInstanceMigrationDocument migrationDocument = HistoricCaseInstanceMigrationDocumentImpl.fromJson( batch.getBatchDocumentJson(engineConfiguration.getEngineCfgKey())); - String exceptionMessage = null; try { migrationManager.migrateHistoricCaseInstance(batchPart.getScopeId(), migrationDocument, commandContext); - } catch (FlowableException e) { - exceptionMessage = e.getMessage(); - } + } catch (Exception e) { + String exceptionMessage = e.getMessage(); + + engineConfiguration.getCommandExecutor().execute(new Command<>() { + @Override + public Void execute(CommandContext commandContext) { + CommandConfig commandConfig = engineConfiguration.getCommandExecutor().getDefaultConfig().transactionRequiresNew(); + return engineConfiguration.getCommandExecutor().execute(commandConfig, new Command<>() { + @Override + public Void execute(CommandContext commandContext2) { + String resultAsJsonString = prepareResultAsJsonString(exceptionMessage, e); + batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); - String resultAsJsonString = prepareResultAsJsonString(exceptionMessage); - - if (exceptionMessage != null) { - batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); - } else { - batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); + return null; + } + }); + } + }); + + FlowableBatchPartMigrationException wrappedException = new FlowableBatchPartMigrationException(e.getMessage(), e); + wrappedException.setIgnoreFailedJob(true); + throw wrappedException; } + + String resultAsJsonString = prepareResultAsJsonString(); + batchService.completeBatchPart(batchPartId, CaseInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); } - protected static String prepareResultAsJsonString(String exceptionMessage) { + protected String prepareResultAsJsonString(String exceptionMessage, Exception e) { 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); - } + objectNode.put(BATCH_RESULT_STATUS_LABEL, CaseInstanceBatchMigrationResult.RESULT_FAIL); + objectNode.put(BATCH_RESULT_MESSAGE_LABEL, exceptionMessage); + objectNode.put(BATCH_RESULT_STACKTRACE_LABEL, getExceptionStacktrace(e)); + return objectNode.toString(); + } + + protected String prepareResultAsJsonString() { + ObjectNode objectNode = getObjectMapper().createObjectNode(); + objectNode.put(BATCH_RESULT_STATUS_LABEL, CaseInstanceBatchMigrationResult.RESULT_SUCCESS); return objectNode.toString(); } + protected String getExceptionStacktrace(Throwable exception) { + StringWriter stringWriter = new StringWriter(); + exception.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } } \ No newline at end of file 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 983cc543cc5..50c6269390a 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 @@ -14,11 +14,9 @@ package org.flowable.cmmn.engine.impl.migration; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import org.flowable.batch.api.Batch; import org.flowable.batch.api.BatchPart; @@ -480,6 +478,7 @@ public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, job.setScopeType(ScopeTypes.CMMN); job.setJobHandlerConfiguration(CaseInstanceMigrationJobHandler.getHandlerCfgForBatchPartId(batchPart.getId())); jobService.createAsyncJob(job, false); + job.setRetries(0); jobService.scheduleAsyncJob(job); } @@ -488,6 +487,7 @@ public Batch batchMigrateCaseInstancesOfCaseDefinition(String caseDefinitionId, TimerJobEntity timerJob = timerJobService.createTimerJob(); timerJob.setJobType(JobEntity.JOB_TYPE_TIMER); timerJob.setRevision(1); + timerJob.setRetries(0); timerJob.setJobHandlerType(CaseInstanceMigrationStatusJobHandler.TYPE); timerJob.setJobHandlerConfiguration(CaseInstanceMigrationJobHandler.getHandlerCfgForBatchId(batch.getId())); timerJob.setScopeType(ScopeTypes.CMMN); 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 8ae7dbefec8..596346cf036 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 @@ -28,6 +28,7 @@ import org.flowable.cmmn.api.runtime.CaseInstance; import org.flowable.cmmn.api.runtime.PlanItemInstance; import org.flowable.cmmn.api.runtime.PlanItemInstanceState; +import org.flowable.cmmn.engine.impl.job.CaseInstanceMigrationJobHandler; import org.flowable.cmmn.engine.impl.job.CaseInstanceMigrationStatusJobHandler; import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationDocumentBuilderImpl; import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; @@ -95,10 +96,16 @@ void testCaseInstanceBatchMigrationSuccess() { 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); } + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); assertAfterMigrationState(caseInstance1, destinationDefinition, caseInstance1AfterMigration, 2); assertAfterMigrationState(caseInstance2, destinationDefinition, caseInstance2AfterMigration, 2); @@ -176,11 +183,101 @@ void testCaseInstanceBatchMigrationWithError() { 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(cmmnManagementService.createJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + + assertAfterMigrationState(caseInstance1, caseDefinitionVersion1, caseInstance1AfterMigration, 1); + assertAfterMigrationState(caseInstance2, caseDefinitionVersion1, caseInstance2AfterMigration, 1); + + cmmnManagementService.deleteBatch(batch.getId()); + } + + @Test + void testCaseInstanceBatchMigrationWithWrongMapping() { + // 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(); + + CmmnDeployment deployment = cmmnRepositoryService.createDeployment() + .name("test1") + .addClasspathResource("org/flowable/cmmn/test/migration/two-task.cmmn.xml") + .deploy(); + + CaseDefinition caseDefinitionVersion2 = cmmnRepositoryService.createCaseDefinitionQuery() + .deploymentId(deployment.getId()) + .singleResult(); + + CaseInstanceMigrationDocumentBuilder migrationDoc = new CaseInstanceMigrationDocumentBuilderImpl() + .setCaseDefinitionToMigrateTo(caseDefinitionVersion2.getId()) + .addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("nonExisting")); + + Batch batch = cmmnMigrationService.createCaseInstanceMigrationBuilderFromCaseInstanceMigrationDocument(migrationDoc.build()) + .batchMigrateCaseInstances(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(); + + assertThat(cmmnManagementService.createJobQuery().handlerType(CaseInstanceMigrationJobHandler.TYPE).list()).hasSize(2); + + // WHEN + // Start async executor to process the batches + CmmnJobTestHelper.waitForJobExecutorToProcessAllAsyncJobs(cmmnEngineConfiguration, 5000L, 500L, true); + assertThat(CmmnJobTestHelper.areJobsAvailable(cmmnManagementService)).isFalse(); + + CaseInstance caseInstance1AfterMigration = cmmnRuntimeService.createCaseInstanceQuery() + .caseInstanceId(caseInstance1.getId()) + .singleResult(); + CaseInstance caseInstance2AfterMigration = cmmnRuntimeService.createCaseInstanceQuery() + .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.getResult()).isEqualTo(CaseInstanceBatchMigrationResult.RESULT_FAIL); + assertThat(part.getMigrationMessage()).contains("Cannot find plan item with definition id 'nonExisting'"); + assertThat(part.getMigrationStacktrace()).contains("Cannot find plan item with definition id 'nonExisting'"); } + + assertThat(cmmnManagementService.createJobQuery().handlerType(CaseInstanceMigrationJobHandler.TYPE).list()).hasSize(0); + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); assertAfterMigrationState(caseInstance1, caseDefinitionVersion1, caseInstance1AfterMigration, 1); assertAfterMigrationState(caseInstance2, caseDefinitionVersion1, caseInstance2AfterMigration, 1); 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 index d92d9d5fde9..c087b5bd7af 100644 --- 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 @@ -99,8 +99,6 @@ void testHistoricCaseInstanceBatchMigrationSuccess() { 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); } @@ -197,11 +195,19 @@ void testHistoricCaseInstanceBatchMigrationWithError() { 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); + assertThat(part.getMigrationMessage()).contains("Tenant mismatch between"); + assertThat(part.getMigrationStacktrace()).contains("Tenant mismatch between"); } + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance1.getId()).list()).hasSize(0); + + assertThat(cmmnManagementService.createJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createTimerJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); + assertThat(cmmnManagementService.createDeadLetterJobQuery().scopeId(caseInstance2.getId()).list()).hasSize(0); assertAfterMigrationState(2, caseInstance1, caseDefinitionVersion1, caseInstance1AfterMigration, 1); assertAfterMigrationState(2, caseInstance2, caseDefinitionVersion1, caseInstance2AfterMigration, 1); diff --git a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/FlowableBatchPartMigrationException.java b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/FlowableBatchPartMigrationException.java new file mode 100644 index 00000000000..71aa1f83544 --- /dev/null +++ b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/FlowableBatchPartMigrationException.java @@ -0,0 +1,36 @@ +/* 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.common.engine.api; + +public class FlowableBatchPartMigrationException extends FlowableException { + + private static final long serialVersionUID = 1L; + + protected boolean ignoreFailedJob; + + public FlowableBatchPartMigrationException(String message, Throwable cause) { + super(message, cause); + } + + public FlowableBatchPartMigrationException(String message) { + super(message); + } + + public boolean isIgnoreFailedJob() { + return ignoreFailedJob; + } + + public void setIgnoreFailedJob(boolean ignoreFailedJob) { + this.ignoreFailedJob = ignoreFailedJob; + } +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetProcessInstanceMigrationBatchResultCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetProcessInstanceMigrationBatchResultCmd.java index 6e52dd680c7..a5c9ee42da7 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetProcessInstanceMigrationBatchResultCmd.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetProcessInstanceMigrationBatchResultCmd.java @@ -34,6 +34,7 @@ public class GetProcessInstanceMigrationBatchResultCmd implements Command() { + @Override + public Void execute(CommandContext commandContext) { + CommandConfig commandConfig = processEngineConfiguration.getCommandExecutor().getDefaultConfig().transactionRequiresNew(); + return processEngineConfiguration.getCommandExecutor().execute(commandConfig, new Command<>() { + @Override + public Void execute(CommandContext commandContext2) { + String resultAsJsonString = prepareResultAsJsonString(exceptionMessage, e); + batchService.completeBatchPart(batchPartId, ProcessInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); - String resultAsJsonString = prepareResultAsJsonString(exceptionMessage); - - if (exceptionMessage != null) { - batchService.completeBatchPart(batchPartId, ProcessInstanceBatchMigrationResult.RESULT_FAIL, resultAsJsonString); - } else { - batchService.completeBatchPart(batchPartId, ProcessInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); + return null; + } + }); + } + }); + + FlowableBatchPartMigrationException wrappedException = new FlowableBatchPartMigrationException(e.getMessage(), e); + wrappedException.setIgnoreFailedJob(true); + throw wrappedException; } + + String resultAsJsonString = prepareResultAsJsonString(); + batchService.completeBatchPart(batchPartId, ProcessInstanceBatchMigrationResult.RESULT_SUCCESS, resultAsJsonString); } - protected static String prepareResultAsJsonString(String exceptionMessage) { + protected String prepareResultAsJsonString(String exceptionMessage, Exception e) { ObjectNode objectNode = getObjectMapper().createObjectNode(); - if (exceptionMessage == null) { - objectNode.put(BATCH_RESULT_STATUS_LABEL, ProcessInstanceBatchMigrationResult.RESULT_SUCCESS); - } else { - objectNode.put(BATCH_RESULT_STATUS_LABEL, ProcessInstanceBatchMigrationResult.RESULT_FAIL); - objectNode.put(BATCH_RESULT_MESSAGE_LABEL, exceptionMessage); - } + objectNode.put(BATCH_RESULT_STATUS_LABEL, ProcessInstanceBatchMigrationResult.RESULT_FAIL); + objectNode.put(BATCH_RESULT_MESSAGE_LABEL, exceptionMessage); + objectNode.put(BATCH_RESULT_STACKTRACE_LABEL, getExceptionStacktrace(e)); + return objectNode.toString(); + } + + protected String prepareResultAsJsonString() { + ObjectNode objectNode = getObjectMapper().createObjectNode(); + objectNode.put(BATCH_RESULT_STATUS_LABEL, ProcessInstanceBatchMigrationResult.RESULT_SUCCESS); return objectNode.toString(); } + protected String getExceptionStacktrace(Throwable exception) { + StringWriter stringWriter = new StringWriter(); + exception.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } } \ No newline at end of file diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java index 6d08ce70647..3298b85a7b0 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java @@ -349,6 +349,7 @@ public Batch batchMigrateProcessInstancesOfProcessDefinition(String sourceProcDe job.setProcessInstanceId(processInstance.getId()); job.setJobHandlerConfiguration(ProcessInstanceMigrationJobHandler.getHandlerCfgForBatchPartId(batchPart.getId())); jobService.createAsyncJob(job, false); + job.setRetries(0); jobService.scheduleAsyncJob(job); } @@ -357,6 +358,7 @@ public Batch batchMigrateProcessInstancesOfProcessDefinition(String sourceProcDe TimerJobEntity timerJob = timerJobService.createTimerJob(); timerJob.setJobType(JobEntity.JOB_TYPE_TIMER); timerJob.setRevision(1); + timerJob.setRetries(0); timerJob.setJobHandlerType(ProcessInstanceMigrationStatusJobHandler.TYPE); timerJob.setJobHandlerConfiguration(ProcessInstanceMigrationJobHandler.getHandlerCfgForBatchId(batch.getId())); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceBatchMigrationPartResult.java b/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceBatchMigrationPartResult.java index 53c96277b75..d26e2cade89 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceBatchMigrationPartResult.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/migration/ProcessInstanceBatchMigrationPartResult.java @@ -22,6 +22,7 @@ public class ProcessInstanceBatchMigrationPartResult { protected String sourceProcessDefinitionId; protected String targetProcessDefinitionId; protected String migrationMessage; + protected String migrationStacktrace; public String getBatchId() { return batchId; @@ -78,4 +79,12 @@ public String getMigrationMessage() { public void setMigrationMessage(String migrationMessage) { this.migrationMessage = migrationMessage; } + + public String getMigrationStacktrace() { + return migrationStacktrace; + } + + public void setMigrationStacktrace(String migrationStacktrace) { + this.migrationStacktrace = migrationStacktrace; + } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationBatchTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationBatchTest.java index f17ea63cb40..58904a701a7 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationBatchTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationBatchTest.java @@ -36,12 +36,14 @@ import org.flowable.batch.api.BatchPart; import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.engine.impl.jobexecutor.ProcessInstanceMigrationStatusJobHandler; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.test.JobTestHelper; import org.flowable.engine.impl.test.PluggableFlowableTestCase; import org.flowable.engine.migration.ProcessInstanceBatchMigrationPartResult; import org.flowable.engine.migration.ProcessInstanceBatchMigrationResult; import org.flowable.engine.migration.ProcessInstanceMigrationBuilder; import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.test.api.runtime.changestate.ChangeStateEventListener; import org.flowable.job.api.Job; @@ -91,7 +93,7 @@ public void testProcessMigrationBatchMissingMapping() { assertThat(task.getTaskDefinitionKey()).isEqualTo("userTask2Id"); assertThat(task.getProcessDefinitionId()).isEqualTo(version1ProcessDef.getId()); - //Deploy second version of the process + // Deploy second version of the process ProcessDefinition version2ProcessDef = deployProcessDefinition("my deploy", "org/flowable/engine/test/api/runtime/migration/one-task-simple-process.bpmn20.xml"); @@ -162,9 +164,28 @@ public void testProcessMigrationBatchMissingMapping() { task = taskService.createTaskQuery().processInstanceId(processInstance1.getId()).singleResult(); assertThat(task.getTaskDefinitionKey()).isEqualTo("userTask2Id"); assertThat(task.getProcessDefinitionId()).isEqualTo(version1ProcessDef.getId()); + List childExecutions = runtimeService.createExecutionQuery().processInstanceId(processInstance1.getId()).list(); + assertThat(childExecutions).hasSize(2); + for (Execution childExecution : childExecutions) { + assertThat(((ExecutionEntity) childExecution).getProcessDefinitionId()).isEqualTo(version1ProcessDef.getId()); + } + task = taskService.createTaskQuery().processInstanceId(processInstance2.getId()).singleResult(); assertThat(task.getTaskDefinitionKey()).isEqualTo("userTask2Id"); assertThat(task.getProcessDefinitionId()).isEqualTo(version1ProcessDef.getId()); + childExecutions = runtimeService.createExecutionQuery().processInstanceId(processInstance2.getId()).list(); + assertThat(childExecutions).hasSize(2); + for (Execution childExecution : childExecutions) { + assertThat(((ExecutionEntity) childExecution).getProcessDefinitionId()).isEqualTo(version1ProcessDef.getId()); + } + + assertThat(managementService.createJobQuery().processInstanceId(processInstance1.getId()).list()).hasSize(0); + assertThat(managementService.createTimerJobQuery().processInstanceId(processInstance1.getId()).list()).hasSize(0); + assertThat(managementService.createDeadLetterJobQuery().processInstanceId(processInstance1.getId()).list()).hasSize(0); + + assertThat(managementService.createJobQuery().processInstanceId(processInstance2.getId()).list()).hasSize(0); + assertThat(managementService.createTimerJobQuery().processInstanceId(processInstance2.getId()).list()).hasSize(0); + assertThat(managementService.createDeadLetterJobQuery().processInstanceId(processInstance2.getId()).list()).hasSize(0); completeProcessInstanceTasks(processInstance1.getId()); completeProcessInstanceTasks(processInstance2.getId()); @@ -259,12 +280,14 @@ public void testProcessMigrationBatchPartialMissingMapping() { assertThat(part.getStatus()).isEqualTo(ProcessInstanceBatchMigrationResult.STATUS_COMPLETED); assertThat(part.getResult()).isEqualTo(ProcessInstanceBatchMigrationResult.RESULT_SUCCESS); assertThat(part.getMigrationMessage()).isNull(); + assertThat(part.getMigrationStacktrace()).isNull(); } for (ProcessInstanceBatchMigrationPartResult part : migrationResult.getFailedMigrationParts()) { assertThat(part.getStatus()).isEqualTo(ProcessInstanceBatchMigrationResult.STATUS_COMPLETED); assertThat(part.getResult()).isEqualTo(ProcessInstanceBatchMigrationResult.RESULT_FAIL); assertThat(part.getMigrationMessage()).isEqualTo("Migration Activity mapping missing for activity definition Id:'userTask2Id' or its MI Parent"); + assertThat(part.getMigrationStacktrace()).contains("Migration Activity mapping missing"); } // Confirm the migration @@ -276,6 +299,14 @@ public void testProcessMigrationBatchPartialMissingMapping() { // This task migrated assertThat(task.getProcessDefinitionId()).isEqualTo(version2ProcessDef.getId()); + + assertThat(managementService.createJobQuery().processInstanceId(processInstance1.getId()).list()).hasSize(0); + assertThat(managementService.createTimerJobQuery().processInstanceId(processInstance1.getId()).list()).hasSize(0); + assertThat(managementService.createDeadLetterJobQuery().processInstanceId(processInstance1.getId()).list()).hasSize(0); + + assertThat(managementService.createJobQuery().processInstanceId(processInstance2.getId()).list()).hasSize(0); + assertThat(managementService.createTimerJobQuery().processInstanceId(processInstance2.getId()).list()).hasSize(0); + assertThat(managementService.createDeadLetterJobQuery().processInstanceId(processInstance2.getId()).list()).hasSize(0); completeProcessInstanceTasks(processInstance1.getId()); completeProcessInstanceTasks(processInstance2.getId()); @@ -458,6 +489,18 @@ public void testProcessMigrationBatchTwentyMixedSuccessAndFails() { assertThat(task.getTaskDefinitionKey()).isEqualTo("userTask2Id"); assertThat(task.getProcessDefinitionId()).isEqualTo(version1ProcessDef.getId()); } + + for (String processInstanceId : successInstances) { + assertThat(managementService.createJobQuery().processInstanceId(processInstanceId).list()).hasSize(0); + assertThat(managementService.createTimerJobQuery().processInstanceId(processInstanceId).list()).hasSize(0); + assertThat(managementService.createDeadLetterJobQuery().processInstanceId(processInstanceId).list()).hasSize(0); + } + + for (String processInstanceId : failedInstances) { + assertThat(managementService.createJobQuery().processInstanceId(processInstanceId).list()).hasSize(0); + assertThat(managementService.createTimerJobQuery().processInstanceId(processInstanceId).list()).hasSize(0); + assertThat(managementService.createDeadLetterJobQuery().processInstanceId(processInstanceId).list()).hasSize(0); + } // Complete the processes for (String processInstanceId : successInstances) { diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/AcquiredExternalWorkerJobImpl.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/AcquiredExternalWorkerJobImpl.java index f2bf76af846..6751d3e94b8 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/AcquiredExternalWorkerJobImpl.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/AcquiredExternalWorkerJobImpl.java @@ -155,4 +155,5 @@ public String getLockOwner() { public Date getLockExpirationTime() { return job.getLockExpirationTime(); } + } diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java index 3681c9a7bef..f1ba4cea853 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java @@ -475,6 +475,13 @@ public void unacquireWithDecrementRetries(JobInfo job, Throwable exception) { } } + + @Override + public void deleteExecutableJob(JobInfo job) { + if (job instanceof JobEntity) { + jobServiceConfiguration.getJobEntityManager().delete((JobEntity) job); + } + } protected String getExceptionStacktrace(Throwable exception) { StringWriter stringWriter = new StringWriter(); diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/ExecuteAsyncRunnable.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/ExecuteAsyncRunnable.java index 8fdaba45a2f..6044d81f1c4 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/ExecuteAsyncRunnable.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/ExecuteAsyncRunnable.java @@ -15,11 +15,13 @@ import java.util.ArrayList; import java.util.List; +import org.flowable.common.engine.api.FlowableBatchPartMigrationException; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableOptimisticLockingException; import org.flowable.common.engine.api.tenant.TenantContext; import org.flowable.common.engine.impl.context.Context; import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.common.engine.impl.tenant.CurrentTenant; import org.flowable.job.api.HistoryJob; @@ -197,6 +199,24 @@ public Void execute(CommandContext commandContext) { } protected void handleFailedJob(final Throwable exception) { + if (exception instanceof FlowableBatchPartMigrationException && ((FlowableBatchPartMigrationException) exception).isIgnoreFailedJob()) { + jobServiceConfiguration.getCommandExecutor().execute(new Command<>() { + @Override + public Void execute(CommandContext commandContext) { + CommandConfig commandConfig = jobServiceConfiguration.getCommandExecutor().getDefaultConfig().transactionRequiresNew(); + return jobServiceConfiguration.getCommandExecutor().execute(commandConfig, new Command<>() { + @Override + public Void execute(CommandContext commandContext2) { + jobServiceConfiguration.getJobManager().deleteExecutableJob(job); + return null; + } + }); + } + }); + + return; + } + for (AsyncRunnableExecutionExceptionHandler asyncRunnableExecutionExceptionHandler : asyncRunnableExecutionExceptionHandlers) { if (asyncRunnableExecutionExceptionHandler.handleException(this.jobServiceConfiguration, this.job, exception)) { diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/JobManager.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/JobManager.java index d7187675fc9..89e1f044572 100644 --- a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/JobManager.java +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/JobManager.java @@ -127,6 +127,8 @@ public interface JobManager { */ HistoryJobEntity moveDeadLetterJobToHistoryJob(DeadLetterJobEntity deadLetterJobEntity, int retries); + void deleteExecutableJob(JobInfo job); + /** * schedules a {@link HistoryJobEntity}, meaning it will be scheduled (inserted in the database/put on a queue/...) to be executed at a later point in time. */