
org.camunda.bpm.engine.impl.migration.MigrateProcessInstanceCmd Maven / Gradle / Ivy
/* 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.camunda.bpm.engine.impl.migration;
import static org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.camunda.bpm.engine.BadUserRequestException;
import org.camunda.bpm.engine.impl.ProcessEngineLogger;
import org.camunda.bpm.engine.impl.context.Context;
import org.camunda.bpm.engine.impl.interceptor.Command;
import org.camunda.bpm.engine.impl.interceptor.CommandContext;
import org.camunda.bpm.engine.impl.migration.instance.MigratingActivityInstance;
import org.camunda.bpm.engine.impl.migration.instance.MigratingActivityInstanceWalker;
import org.camunda.bpm.engine.impl.migration.instance.MigratingActivityInstanceBranch;
import org.camunda.bpm.engine.impl.migration.instance.MigratingProcessInstance;
import org.camunda.bpm.engine.impl.migration.instance.parser.MigratingInstanceParser;
import org.camunda.bpm.engine.impl.migration.validation.instance.MigratingActivityInstanceValidationReportImpl;
import org.camunda.bpm.engine.impl.migration.validation.instance.MigratingActivityInstanceValidator;
import org.camunda.bpm.engine.impl.migration.validation.instance.MigratingProcessInstanceValidationReportImpl;
import org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.camunda.bpm.engine.impl.pvm.PvmActivity;
import org.camunda.bpm.engine.impl.pvm.process.ScopeImpl;
import org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl;
import org.camunda.bpm.engine.impl.tree.FlowScopeWalker;
import org.camunda.bpm.engine.impl.tree.ReferenceWalker;
import org.camunda.bpm.engine.impl.tree.TreeVisitor;
import org.camunda.bpm.engine.impl.util.EnsureUtil;
import org.camunda.bpm.engine.migration.MigrationPlan;
import org.camunda.bpm.engine.runtime.ActivityInstance;
/**
* How migration works:
*
*
* - Validate migration instructions.
*
- Delete activity instances that are not going to be migrated, invoking execution listeners
* and io mappings. This is performed in a bottom-up fashion in the activity instance tree and ensures
* that the "upstream" tree is always consistent with respect to the old process definition.
*
- Migrate and create activity instances. Creation invokes execution listeners
* and io mappings. This is performed in a top-down fashion in the activity instance tree and
* ensures that the "upstream" tree is always consistent with respect to the new process definition.
*
* @author Thorben Lindhauer
*/
public class MigrateProcessInstanceCmd implements Command
{
protected static final MigrationLogger LOGGER = ProcessEngineLogger.MIGRATION_LOGGER;
protected MigrationPlanExecutionBuilderImpl migrationPlanExecutionBuilder;
public MigrateProcessInstanceCmd(MigrationPlanExecutionBuilderImpl migrationPlanExecutionBuilder) {
this.migrationPlanExecutionBuilder = migrationPlanExecutionBuilder;
}
public Void execute(CommandContext commandContext) {
MigrationPlan migrationPlan = migrationPlanExecutionBuilder.getMigrationPlan();
List processInstanceIds = migrationPlanExecutionBuilder.getProcessInstanceIds();
ensureNotNull(BadUserRequestException.class, "Migration plan cannot be null", "migration plan", migrationPlan);
ensureNotNull(BadUserRequestException.class, "Process instance ids cannot be null", "process instance ids", processInstanceIds);
ProcessDefinitionEntity targetProcessDefinition = commandContext.getProcessEngineConfiguration()
.getDeploymentCache().findDeployedProcessDefinitionById(migrationPlan.getTargetProcessDefinitionId());
for (String processInstanceId : processInstanceIds) {
migrateProcessInstance(commandContext, processInstanceId, migrationPlan, targetProcessDefinition);
}
return null;
}
public Void migrateProcessInstance(CommandContext commandContext, String processInstanceId, MigrationPlan migrationPlan, ProcessDefinitionEntity targetProcessDefinition) {
ensureNotNull(BadUserRequestException.class, "Process instance id cannot be null", "process instance id", processInstanceId);
ExecutionEntity processInstance = commandContext.getExecutionManager().findExecutionById(processInstanceId);
ensureProcessInstanceExist(processInstanceId, processInstance);
ensureSameProcessDefinition(processInstance, migrationPlan.getSourceProcessDefinitionId());
MigratingProcessInstanceValidationReportImpl processInstanceReport = new MigratingProcessInstanceValidationReportImpl();
// Initialize migration: match migration instructions to activity instances and collect required entities
MigratingInstanceParser migratingInstanceParser = new MigratingInstanceParser(Context.getProcessEngineConfiguration().getProcessEngine());
MigratingProcessInstance migratingProcessInstance = migratingInstanceParser.parse(processInstance.getId(), migrationPlan, processInstanceReport);
validateInstructions(commandContext, migratingProcessInstance, processInstanceReport);
if (processInstanceReport.hasFailures()) {
throw LOGGER.failingMigratingProcessInstanceValidation(processInstanceReport);
}
deleteUnmappedActivityInstances(migratingProcessInstance);
migrateProcessInstance(migratingProcessInstance);
return null;
}
/**
* delete unmapped instances in a bottom-up fashion (similar to deleteCascade and regular BPMN execution)
*/
protected void deleteUnmappedActivityInstances(MigratingProcessInstance migratingProcessInstance) {
final Set visitedActivityInstances = new HashSet();
Set leafInstances = collectLeafInstances(migratingProcessInstance);
for (MigratingActivityInstance leafInstance : leafInstances) {
MigratingActivityInstanceWalker walker = new MigratingActivityInstanceWalker(leafInstance);
walker.addPreVisitor(new TreeVisitor() {
@Override
public void visit(MigratingActivityInstance currentInstance) {
visitedActivityInstances.add(currentInstance);
if (!currentInstance.migrates()) {
Set children = new HashSet(currentInstance.getChildren());
MigratingActivityInstance parent = currentInstance.getParent();
// 1. detach children
currentInstance.detachChildren();
// 2. manipulate execution tree (i.e. remove this instance)
currentInstance.remove();
// 3. reconnect parent and children
for (MigratingActivityInstance child : children) {
child.attachState(parent);
}
}
else {
currentInstance.removeUnmappedDependentInstances();
}
}
});
walker.walkUntil(new ReferenceWalker.WalkCondition() {
@Override
public boolean isFulfilled(MigratingActivityInstance element) {
// walk until top of instance tree is reached or until
// a node is reached for which we have not yet visited every child
return element == null || !visitedActivityInstances.containsAll(element.getChildren());
}
});
}
}
protected Set collectLeafInstances(MigratingProcessInstance migratingProcessInstance) {
Set leafInstances = new HashSet();
for (MigratingActivityInstance migratingActivityInstance : migratingProcessInstance.getMigratingActivityInstances()) {
if (migratingActivityInstance.getChildren().isEmpty()) {
leafInstances.add(migratingActivityInstance);
}
}
return leafInstances;
}
protected void validateInstructions(CommandContext commandContext, MigratingProcessInstance migratingProcessInstance, MigratingProcessInstanceValidationReportImpl processInstanceReport) {
List migratingActivityInstanceValidators = commandContext.getProcessEngineConfiguration().getMigratingActivityInstanceValidators();
for (MigratingActivityInstance migratingActivityInstance : migratingProcessInstance.getMigratingActivityInstances()) {
MigratingActivityInstanceValidationReportImpl instanceReport = validateActivityInstance(migratingActivityInstance, migratingProcessInstance, migratingActivityInstanceValidators);
if (instanceReport.hasFailures()) {
processInstanceReport.addInstanceReport(instanceReport);
}
}
}
protected MigratingActivityInstanceValidationReportImpl validateActivityInstance(MigratingActivityInstance migratingActivityInstance, MigratingProcessInstance migratingProcessInstance, List migratingActivityInstanceValidators) {
MigratingActivityInstanceValidationReportImpl instanceReport = new MigratingActivityInstanceValidationReportImpl(migratingActivityInstance);
for (MigratingActivityInstanceValidator migratingActivityInstanceValidator : migratingActivityInstanceValidators) {
migratingActivityInstanceValidator.validate(migratingActivityInstance, migratingProcessInstance, instanceReport);
}
return instanceReport;
}
/**
* Migrate activity instances to their new activities and process definition. Creates new
* scope instances as necessary.
*/
protected void migrateProcessInstance(MigratingProcessInstance migratingProcessInstance) {
MigratingActivityInstance rootActivityInstance =
migratingProcessInstance.getMigratingInstance(migratingProcessInstance.getProcessInstanceId());
MigratingActivityInstanceBranch scopeExecutionContext = new MigratingActivityInstanceBranch();
scopeExecutionContext.visited(rootActivityInstance);
migrateActivityInstance(scopeExecutionContext, rootActivityInstance);
}
protected void migrateActivityInstance(
MigratingActivityInstanceBranch migratingInstanceBranch,
MigratingActivityInstance migratingActivityInstance) {
ActivityInstance activityInstance = migratingActivityInstance.getActivityInstance();
if (!activityInstance.getId().equals(activityInstance.getProcessInstanceId())) {
final MigratingActivityInstance parentMigratingInstance = migratingActivityInstance.getParent();
ScopeImpl targetScope = migratingActivityInstance.getTargetScope();
ScopeImpl targetFlowScope = targetScope.getFlowScope();
ScopeImpl parentActivityInstanceTargetScope = parentMigratingInstance.getTargetScope();
if (targetFlowScope != parentActivityInstanceTargetScope) {
// create intermediate scopes
// 1. manipulate execution tree
// determine the list of ancestor scopes (parent, grandparent, etc.) for which
// no executions exist yet
List nonExistingScopes = collectNonExistingFlowScopes(targetFlowScope, migratingInstanceBranch);
// get the closest ancestor scope that is instantiated already
ScopeImpl existingScope = nonExistingScopes.isEmpty() ?
targetFlowScope :
nonExistingScopes.get(0).getFlowScope();
// and its scope instance
MigratingActivityInstance ancestorScopeInstance = migratingInstanceBranch.getInstance(existingScope);
// Instantiate the scopes as children of the scope execution
instantiateScopes(ancestorScopeInstance, migratingInstanceBranch, nonExistingScopes);
MigratingActivityInstance targetFlowScopeInstance = migratingInstanceBranch.getInstance(targetFlowScope);
// 2. detach activity instance
// The order of steps 1 and 2 avoids intermediate execution tree compaction
// which in turn could overwrite some dependent instances (e.g. variables)
migratingActivityInstance.detachState();
// 3. attach to newly created execution
migratingActivityInstance.attachState(targetFlowScopeInstance);
}
}
// 4. update state (e.g. activity id)
migratingActivityInstance.migrateState();
// 5. migrate instance state other than execution-tree structure
migratingActivityInstance.migrateDependentEntities();
// Let activity instances on the same level of subprocess share the same execution context
// of newly created scope executions.
// This ensures that newly created scope executions
// * are reused to attach activity instances to when the activity instances share a
// common ancestor path to the process instance
// * are not reused when activity instances are in unrelated branches of the execution tree
migratingInstanceBranch = migratingInstanceBranch.copy();
migratingInstanceBranch.visited(migratingActivityInstance);
Set children = new HashSet(migratingActivityInstance.getChildren());
for (MigratingActivityInstance childInstance : children) {
migrateActivityInstance(migratingInstanceBranch, childInstance);
}
}
/**
* Returns a list of flow scopes from the given scope until a scope is reached that is already present in the given
* {@link MigratingActivityInstanceBranch} (exclusive). The order of the returned list is top-down, i.e. the highest scope
* is the first element of the list.
*/
protected List collectNonExistingFlowScopes(ScopeImpl scope, final MigratingActivityInstanceBranch migratingExecutionBranch) {
FlowScopeWalker walker = new FlowScopeWalker(scope);
final List result = new LinkedList();
walker.addPreVisitor(new TreeVisitor() {
@Override
public void visit(ScopeImpl obj) {
result.add(0, obj);
}
});
walker.walkWhile(new ReferenceWalker.WalkCondition() {
@Override
public boolean isFulfilled(ScopeImpl element) {
return migratingExecutionBranch.hasInstance(element);
}
});
return result;
}
/**
* Creates scope executions for the given list of scopes;
* Registers these executions with the migrating execution branch;
*
* @param ancestorScopeInstance the instance for the scope that the scopes to instantiate
* are subordinates to
* @param executionBranch the migrating execution branch that manages scopes and their executions
* @param scopesToInstantiate a list of hierarchical scopes to instantiate, ordered top-down
*/
protected void instantiateScopes(MigratingActivityInstance ancestorScopeInstance,
MigratingActivityInstanceBranch executionBranch, List scopesToInstantiate) {
if (scopesToInstantiate.isEmpty()) {
return;
}
ExecutionEntity newParentExecution = ancestorScopeInstance.createAttachableExecution();
Map createdExecutions =
newParentExecution.instantiateScopes((List) scopesToInstantiate);
for (ScopeImpl scope : scopesToInstantiate) {
ExecutionEntity createdExecution = (ExecutionEntity) createdExecutions.get(scope);
createdExecution.setActivity(null);
executionBranch.visited(new MigratingActivityInstance(scope, createdExecution));
}
}
protected void ensureProcessInstanceExist(String processInstanceId, ExecutionEntity processInstance) {
if (processInstance == null) {
throw LOGGER.processInstanceDoesNotExist(processInstanceId);
}
}
protected void ensureSameProcessDefinition(ExecutionEntity processInstance, String processDefinitionId) {
if (!processDefinitionId.equals(processInstance.getProcessDefinitionId())) {
throw LOGGER.processDefinitionOfInstanceDoesNotMatchMigrationPlan(processInstance, processDefinitionId);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy