Skip to content

Commit

Permalink
Add support to add a new plan item instance for a reactivation listen…
Browse files Browse the repository at this point in the history
…er or other new plan item definition for historic case migration
  • Loading branch information
tijsrademakers committed Apr 8, 2024
1 parent e1bd136 commit 76970b3
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
* @author Micha Kiener
*/
public interface CaseReactivationBuilder {

/**
* Adds a plan item instance for a new plan item definition. This is mainly needed when no reactivation listener was present in the old case definition id.
*
* @param planItemDefinitionId the plan item definition id for which a new plan item instance will be created in available state
* @return the builder for method chaining
*/
CaseReactivationBuilder addTerminatedPlanItemInstanceForPlanItemDefinition(String planItemDefinitionId);

/**
* Adds a variable to be added to the case before triggering the reactivation event.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@
package org.flowable.cmmn.engine.impl.cmd;

import java.io.Serializable;
import java.util.Date;

import org.apache.commons.lang3.StringUtils;
import org.flowable.cmmn.api.history.HistoricCaseInstance;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.api.runtime.CaseInstanceState;
import org.flowable.cmmn.api.runtime.PlanItemInstanceState;
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity;
import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity;
import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntityManager;
import org.flowable.cmmn.engine.impl.reactivation.CaseReactivationBuilderImpl;
import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil;
import org.flowable.cmmn.engine.impl.util.CommandContextUtil;
import org.flowable.cmmn.model.Case;
import org.flowable.cmmn.model.PlanItem;
import org.flowable.cmmn.model.ReactivateEventListener;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.FlowableIllegalStateException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
Expand Down Expand Up @@ -85,7 +92,8 @@ public CaseInstance execute(CommandContext commandContext) {
}

// if there is an available condition on the reactivation event listener, evaluate it to make sure the listener is available for reactivation
ReactivateEventListener reactivateEventListener = CaseDefinitionUtil.getCase(instance.getCaseDefinitionId()).getReactivateEventListener();
Case caze = CaseDefinitionUtil.getCase(instance.getCaseDefinitionId());
ReactivateEventListener reactivateEventListener = caze.getReactivateEventListener();
String availableConditionExpression = reactivateEventListener.getReactivationAvailableConditionExpression();
if (StringUtils.isNotEmpty(availableConditionExpression)) {
Object listenerAvailable = CommandContextUtil.getCmmnEngineConfiguration()
Expand All @@ -103,6 +111,32 @@ public CaseInstance execute(CommandContext commandContext) {
if (cmmnEngineConfiguration.getIdentityLinkInterceptor() != null) {
cmmnEngineConfiguration.getIdentityLinkInterceptor().handleReactivateCaseInstance(caseInstanceEntity);
}

if (!reactivationBuilder.getTerminatedPlanItemDefinitionIds().isEmpty()) {
for (String planItemDefinitionId : reactivationBuilder.getTerminatedPlanItemDefinitionIds()) {
PlanItem planItem = caze.getPlanModel().findPlanItemForPlanItemDefinitionInPlanFragmentOrDownwards(planItemDefinitionId);
if (planItem == null) {
throw new FlowableException("incorrect plan item definition id passed " + planItemDefinitionId);
}

PlanItemInstanceEntityManager planItemInstanceEntityManager = cmmnEngineConfiguration.getPlanItemInstanceEntityManager();
PlanItemInstanceEntity reactivatedPlanItemInstance = planItemInstanceEntityManager
.createPlanItemInstanceEntityBuilder()
.planItem(planItem)
.caseDefinitionId(caseInstanceEntity.getCaseDefinitionId())
.caseInstanceId(caseInstanceEntity.getId())
.tenantId(caseInstanceEntity.getTenantId())
.create();

Date currentTime = cmmnEngineConfiguration.getClock().getCurrentTime();
reactivatedPlanItemInstance.setState(PlanItemInstanceState.TERMINATED);
reactivatedPlanItemInstance.setEndedTime(currentTime);
reactivatedPlanItemInstance.setTerminatedTime(currentTime);
caseInstanceEntity.getChildPlanItemInstances().add(reactivatedPlanItemInstance);

cmmnEngineConfiguration.getCmmnHistoryManager().recordPlanItemInstanceReactivated(reactivatedPlanItemInstance);
}
}

// record the reactivation of the case in the history manager to also synchronize the new state and last reactivation data to the history
cmmnEngineConfiguration.getCmmnHistoryManager().recordHistoricCaseInstanceReactivated(caseInstanceEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
*/
package org.flowable.cmmn.engine.impl.reactivation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.flowable.cmmn.api.reactivation.CaseReactivationBuilder;
Expand All @@ -30,6 +32,7 @@ public class CaseReactivationBuilderImpl implements CaseReactivationBuilder {

protected final CommandExecutor commandExecutor;
protected final String caseInstanceId;
protected List<String> terminatedPlanItemDefinitionIds = new ArrayList<>();
protected Map<String, Object> variables;
protected Map<String, Object> transientVariables;

Expand All @@ -42,6 +45,10 @@ public String getCaseInstanceId() {
return caseInstanceId;
}

public List<String> getTerminatedPlanItemDefinitionIds() {
return terminatedPlanItemDefinitionIds;
}

public boolean hasVariables() {
return variables != null && variables.size() > 0;
}
Expand All @@ -58,6 +65,12 @@ public Map<String, Object> getTransientVariables() {
return transientVariables;
}

@Override
public CaseReactivationBuilder addTerminatedPlanItemInstanceForPlanItemDefinition(String planItemDefinitionId) {
this.terminatedPlanItemDefinitionIds.add(planItemDefinitionId);
return this;
}

@Override
public CaseReactivationBuilder variable(String name, Object value) {
if (variables == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,85 @@ public void simpleMigrateCaseReactivationTest() {
Authentication.setAuthenticatedUserId(previousUserId);
}
}

@Test
@CmmnDeployment(resources = "org/flowable/cmmn/test/reactivation/Simple_Reactivation_Test_Case_No_Reactivation.cmmn.xml")
public void simpleMigrateCaseWithNoReactivationTest() {
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_Reactivation_Test_Case_Added_Reactivation.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 Reactivation Test Case Added Reactivaton");
assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionVersion()).isEqualTo(2);
assertThat(historicCaseInstanceAfterMigration.getCaseDefinitionDeploymentId()).isEqualTo(newCaseDefinition.getDeploymentId());

CaseInstance reactivatedCase = cmmnHistoryService.createCaseReactivationBuilder(historicCase.getId())
.addTerminatedPlanItemInstanceForPlanItemDefinition("reactivateEventListener1")
.addTerminatedPlanItemInstanceForPlanItemDefinition("humanTask3")
.reactivate();
assertThat(reactivatedCase).isNotNull();

List<PlanItemInstance> 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, 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<HistoricPlanItemInstance> historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery().list();
assertThat(historicPlanItemInstances).isNotNull().hasSize(8);

Map<String, HistoricPlanItemInstance> 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());
}

cmmnTaskService.complete(cmmnTaskService.createTaskQuery().caseInstanceId(reactivatedCase.getId()).singleResult().getId());
assertCaseInstanceEnded(reactivatedCase);

} finally {
if (deployment != null) {
CmmnTestHelper.deleteDeployment(cmmnEngineConfiguration, deployment.getId());
}
Authentication.setAuthenticatedUserId(previousUserId);
}
}

protected HistoricCaseInstance createAndFinishSimpleCase(String caseDefinitionKey) {
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" xmlns:design="http://flowable.org/design" targetNamespace="http://flowable.org/cmmn">
<case id="simpleReactivationTestCase" name="Simple Reactivation Test Case Added Reactivaton" flowable:initiatorVariableName="initiator" flowable:candidateStarterGroups="flowableUser">
<casePlanModel id="onecaseplanmodel1" name="Case plan model" flowable:formFieldValidation="false">
<planItem id="planItem2" name="Stage A" definitionRef="expandedStage1"></planItem>
<planItem id="planItem4" name="Stage B" definitionRef="expandedStage2">
<entryCriterion id="entryCriterion1" sentryRef="sentry1"></entryCriterion>
</planItem>
<planItem id="planItem5" name="Reactivate case" definitionRef="reactivateEventListener1"></planItem>
<planItem id="planItem6" name="Task C" definitionRef="humanTask3">
<itemControl>
<extensionElements>
<flowable:parentCompletionRule type="ignoreIfAvailableOrEnabled"></flowable:parentCompletionRule>
</extensionElements>
</itemControl>
<entryCriterion id="entryCriterion2" sentryRef="sentry2"></entryCriterion>
</planItem>
<sentry id="sentry1">
<planItemOnPart id="sentryOnPart1" sourceRef="planItem2">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<sentry id="sentry2">
<planItemOnPart id="sentryOnPart2" sourceRef="planItem5">
<standardEvent>occur</standardEvent>
</planItemOnPart>
</sentry>
<stage id="expandedStage1" name="Stage A">
<planItem id="planItem1" name="Task A" definitionRef="humanTask1"></planItem>
<humanTask id="humanTask1" name="Task A" />
</stage>
<stage id="expandedStage2" name="Stage B">
<planItem id="planItem3" name="Task B" definitionRef="humanTask2"></planItem>
<humanTask id="humanTask2" name="Task B" />
</stage>
<eventListener id="reactivateEventListener1" name="Reactivate case" flowable:eventType="reactivate">
<extensionElements>
<flowable:defaultReactivationRule ignoreCondition="true"/>
<flowable:startformkey><![CDATA[simpleReactivationCaseActionForm]]></flowable:startformkey>
<flowable:startFormSameDeployment><![CDATA[true]]></flowable:startFormSameDeployment>
<flowable:start-form-field-validation><![CDATA[false]]></flowable:start-form-field-validation>
</extensionElements>
</eventListener>
<humanTask id="humanTask3" name="Task C" />
</casePlanModel>
</case>
</definitions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" xmlns:design="http://flowable.org/design" targetNamespace="http://flowable.org/cmmn">
<case id="simpleReactivationTestCase" name="Simple Reactivation Test Case No Event" flowable:initiatorVariableName="initiator" flowable:candidateStarterGroups="flowableUser">
<casePlanModel id="onecaseplanmodel1" name="Case plan model" flowable:formFieldValidation="false">
<planItem id="planItem2" name="Stage A" definitionRef="expandedStage1"></planItem>
<planItem id="planItem4" name="Stage B" definitionRef="expandedStage2">
<entryCriterion id="entryCriterion1" sentryRef="sentry1"></entryCriterion>
</planItem>
<sentry id="sentry1">
<planItemOnPart id="sentryOnPart1" sourceRef="planItem2">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<stage id="expandedStage1" name="Stage A" flowable:includeInStageOverview="true">
<planItem id="planItem1" name="Task A" definitionRef="humanTask1"></planItem>
<humanTask id="humanTask1" name="Task A" flowable:assignee="${initiator}" />
</stage>
<stage id="expandedStage2" name="Stage B" flowable:includeInStageOverview="true">
<planItem id="planItem3" name="Task B" definitionRef="humanTask2"></planItem>
<humanTask id="humanTask2" name="Task B" flowable:assignee="${initiator}" />
</stage>
</casePlanModel>
</case>
</definitions>

0 comments on commit 76970b3

Please sign in to comment.