提交 9eb05745 编写于 作者: F falko.menge

Added SetProcessDefinitionVersionCmd as discussed in...

Added SetProcessDefinitionVersionCmd as discussed in http://forums.activiti.org/en/viewtopic.php?t=2918
上级 d347f57f
/* 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.activiti.engine.impl.cmd;
import java.io.Serializable;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.deploy.DeploymentCache;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceManager;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl;
/**
* Changes the process definition version of an existing process instance.
*
* Warning:
* This {@link Command} will NOT perform any migration magic and simply set the
* process definition version in the database, assuming that the user knows,
* what he or she is doing.
*
* This is only useful for simple migrations. The new process definition MUST
* have the exact same activity id to make it still run.
*
* Furthermore, activities referenced by sub-executions and jobs that belong to
* the process instance MUST exist in the new process definition version.
*
* If the process instance is not currently waiting but actively running, then
* this would be a case for optimistic locking, meaning either the version
* update or the "real work" wins, i.e., this is a race condition.
*
* @see http://forums.activiti.org/en/viewtopic.php?t=2918
* @author Falko Menge
*/
public class SetProcessDefinitionVersionCmd implements Command<Void>, Serializable {
private static final long serialVersionUID = 1L;
private final String processInstanceId;
private final String newProcessDefinitionId;
public SetProcessDefinitionVersionCmd(String processInstanceId, String newProcessDefinitionId) {
if (processInstanceId == null || processInstanceId.length() < 1) {
throw new ActivitiException("The process instance id is mandatory, but '" + processInstanceId + "' has been provided.");
}
if (newProcessDefinitionId == null || newProcessDefinitionId.length() < 1) {
throw new ActivitiException("The process definition id is mandatory, but '" + newProcessDefinitionId + "' has been provided.");
}
this.processInstanceId = processInstanceId;
this.newProcessDefinitionId = newProcessDefinitionId;
}
public Void execute(CommandContext commandContext) {
// check that the new process definition is just another version of the same
// process definition that the process instance is using
ExecutionEntity processInstance = commandContext
.getExecutionManager()
.findExecutionById(processInstanceId);
if (processInstance == null) {
throw new ActivitiException("No process instance found for id = '" + processInstanceId + "'.");
}
ProcessDefinitionImpl currentProcessDefinitionImpl = processInstance.getProcessDefinition();
DeploymentCache deploymentCache = Context
.getProcessEngineConfiguration()
.getDeploymentCache();
ProcessDefinitionEntity currentProcessDefinition;
if (currentProcessDefinitionImpl instanceof ProcessDefinitionEntity) {
currentProcessDefinition = (ProcessDefinitionEntity) currentProcessDefinitionImpl;
} else {
currentProcessDefinition = deploymentCache.findDeployedProcessDefinitionById(currentProcessDefinitionImpl.getId());
}
ProcessDefinitionEntity newProcessDefinition = deploymentCache.findDeployedProcessDefinitionById(newProcessDefinitionId);
if (newProcessDefinition == null) {
throw new ActivitiException("No process definition found for id = '" + newProcessDefinitionId + "'.");
}
if (!newProcessDefinition.getKey().equals(currentProcessDefinition.getKey())) {
throw new ActivitiException(
"The key of the new process definition " +
"(key = '" + newProcessDefinition.getKey() + "') " +
"is not equal to that of the process definition " +
"(key = '" + currentProcessDefinition.getKey() + "') " +
"currently used by the process instance " +
"(id = '" + processInstanceId + "').");
}
// check that the new process definition version contains the current activity
if (!newProcessDefinition.contains(processInstance.getActivity())) {
throw new ActivitiException(
"The new process definition " +
"(key = '" + newProcessDefinition.getKey() + "') " +
"does not contain the current activity " +
"(id = '" + processInstance.getActivity().getId() + "') " +
"of the process instance " +
"(id = '" + processInstanceId + "').");
}
// switch the process instance to the new process definition version
processInstance.setProcessDefinition(newProcessDefinition);
// switch the historic process instance to the new process definition version
HistoricProcessInstanceManager historicProcessInstanceManager = commandContext.getHistoricProcessInstanceManager();
if (historicProcessInstanceManager.isHistoryEnabled()) {
HistoricProcessInstanceEntity historicProcessInstance = historicProcessInstanceManager.findHistoricProcessInstance(processInstanceId);
historicProcessInstance.setProcessDefinitionId(newProcessDefinitionId);
}
return null;
}
}
......@@ -55,6 +55,7 @@ public class HistoricProcessInstanceEntity extends HistoricScopeInstanceEntity i
persistentState.put("deleteReason", deleteReason);
persistentState.put("endStateName", endActivityId);
persistentState.put("superProcessInstanceId", superProcessInstanceId);
persistentState.put("processDefinitionId", processDefinitionId);
return persistentState;
}
......
<?xml version="1.0" encoding="UTF-8" ?>
<!--
<?xml version="1.0" encoding="UTF-8" ?>
~ 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
......@@ -52,6 +52,7 @@
<update id="updateHistoricProcessInstance" parameterType="org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntity">
update ACT_HI_PROCINST set
PROC_DEF_ID_ = #{processDefinitionId, jdbcType=VARCHAR},
START_TIME_ = #{startTime, jdbcType=TIMESTAMP},
END_TIME_ = #{endTime, jdbcType=TIMESTAMP},
DURATION_ = #{durationInMillis ,jdbcType=BIGINT},
......
/* 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.activiti.engine.test.db;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.cmd.SetProcessDefinitionVersionCmd;
import org.activiti.engine.impl.interceptor.CommandExecutor;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.test.Deployment;
/**
* @author Falko Menge
*/
public class ProcessInstanceMigrationTest extends PluggableActivitiTestCase {
private static final String TEST_PROCESS = "org/activiti/engine/test/db/ProcessInstanceMigrationTest.testSetProcessDefinitionVersion.bpmn20.xml";
private static final String TEST_PROCESS_ACTIVITY_MISSING = "org/activiti/engine/test/db/ProcessInstanceMigrationTest.testSetProcessDefinitionVersionActivityMissing.bpmn20.xml";
public void testSetProcessDefinitionVersionEmptyArguments() {
try {
new SetProcessDefinitionVersionCmd(null, "23");
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("The process instance id is mandatory, but 'null' has been provided.", ae.getMessage());
}
try {
new SetProcessDefinitionVersionCmd("", "23");
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("The process instance id is mandatory, but '' has been provided.", ae.getMessage());
}
try {
new SetProcessDefinitionVersionCmd("42", null);
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("The process definition id is mandatory, but 'null' has been provided.", ae.getMessage());
}
try {
new SetProcessDefinitionVersionCmd("42", "");
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("The process definition id is mandatory, but '' has been provided.", ae.getMessage());
}
}
public void testSetProcessDefinitionVersionNonExistingPI() {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutorTxRequired();
try {
commandExecutor.execute(new SetProcessDefinitionVersionCmd("42", "23"));
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("No process instance found for id = '42'.", ae.getMessage());
}
}
@Deployment(resources = {TEST_PROCESS})
public void testSetProcessDefinitionVersionNonExistingPD() {
// start process instance
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutorTxRequired();
try {
commandExecutor.execute(new SetProcessDefinitionVersionCmd(pi.getId(), "23"));
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("no deployed process definition found with id '23'", ae.getMessage());
}
}
@Deployment(resources = {TEST_PROCESS, "org/activiti/engine/test/db/processOne.bpmn20.xml"})
public void testSetProcessDefinitionVersionDifferentPD() {
// start process instance
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
ProcessDefinition newProcessDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("processOne")
.singleResult();
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutorTxRequired();
try {
commandExecutor.execute(new SetProcessDefinitionVersionCmd(pi.getId(), newProcessDefinition.getId()));
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("The key of the new process definition (key = 'processOne') is not equal to that of the process definition (key = 'receiveTask') currently used by the process instance (id = '", ae.getMessage());
}
}
@Deployment(resources = {TEST_PROCESS})
public void testSetProcessDefinitionVersionActivityMissing() {
// start process instance
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
// check that receive task has been reached
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState1")
.singleResult();
assertNotNull(execution);
// deploy new version of the process definition
org.activiti.engine.repository.Deployment deployment = repositoryService
.createDeployment()
.addClasspathResource(TEST_PROCESS_ACTIVITY_MISSING)
.deploy();
assertEquals(2, repositoryService.createProcessDefinitionQuery().count());
// migrate process instance to new process definition version
ProcessDefinition newProcessDefinition = repositoryService
.createProcessDefinitionQuery()
.latestVersion()
.singleResult();
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutorTxRequired();
SetProcessDefinitionVersionCmd setProcessDefinitionVersionCmd = new SetProcessDefinitionVersionCmd(pi.getId(), newProcessDefinition.getId());
try {
commandExecutor.execute(setProcessDefinitionVersionCmd);
fail("ActivitiException expected");
} catch (ActivitiException ae) {
assertTextPresent("The new process definition (key = 'receiveTask') does not contain the current activity (id = 'waitState1') of the process instance (id = '", ae.getMessage());
}
// undeploy "manually" deployed process definition
repositoryService.deleteDeployment(deployment.getId(), true);
}
@Deployment
public void testSetProcessDefinitionVersion() {
// start process instance
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
// check that receive task has been reached
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState1")
.singleResult();
assertNotNull(execution);
// deploy new version of the process definition
org.activiti.engine.repository.Deployment deployment = repositoryService
.createDeployment()
.addClasspathResource(TEST_PROCESS)
.deploy();
assertEquals(2, repositoryService.createProcessDefinitionQuery().count());
// migrate process instance to new process definition version
ProcessDefinition newProcessDefinition = repositoryService
.createProcessDefinitionQuery()
.latestVersion()
.singleResult();
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutorTxRequired();
commandExecutor.execute(new SetProcessDefinitionVersionCmd(pi.getId(), newProcessDefinition.getId()));
// signal process instance
runtimeService.signal(execution.getId());
// check that the instance now uses the new process definition version
pi = runtimeService
.createProcessInstanceQuery()
.processInstanceId(pi.getId())
.singleResult();
assertEquals(newProcessDefinition.getId(), pi.getProcessDefinitionId());
// check history
HistoricProcessInstance historicPI = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(pi.getId())
.singleResult();
assertEquals(newProcessDefinition.getId(), historicPI.getProcessDefinitionId());
// undeploy "manually" deployed process definition
repositoryService.deleteDeployment(deployment.getId(), true);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="receiveTask">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="waitState1" />
<receiveTask id="waitState1" name="Wait State 1" />
<sequenceFlow id="flow3" sourceRef="waitState1" targetRef="waitState2" />
<receiveTask id="waitState2" name="Wait State 2" />
<sequenceFlow id="flow4" sourceRef="waitState2" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="receiveTask">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="renamedWaitState1" />
<receiveTask id="renamedWaitState1" name="Renamed Wait State 1" />
<sequenceFlow id="flow3" sourceRef="renamedWaitState1" targetRef="waitState2" />
<receiveTask id="waitState2" name="Wait State 2" />
<sequenceFlow id="flow4" sourceRef="waitState2" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册