forked from camunda/camunda-bpm-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): migrate process instances when deploying a new version
related to CAM-5774
- Loading branch information
1 parent
5abfca1
commit 3447ee7
Showing
6 changed files
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
Migration on Deployment of New Process Version | ||
============================================== | ||
|
||
This example demonstrates how to use the Java API for [process instance migration](http://docs.camunda.org/manual/7.5/user-guide/process-engine/process-instance-migration/) to migrate process instances whenever a new version of a process is deployed. | ||
|
||
How to Use It? | ||
-------------- | ||
|
||
1. Download, install, and start up a [Camunda distribution](https://camunda.org/download/) | ||
2. Checkout the project with Git | ||
3. Import the project into your IDE | ||
4. Build it with maven | ||
5. Deploy the resulting web application to the application server | ||
6. Go to Camunda Tasklist ([http://localhost:8080/camunda/app/tasklist/default/#/](http://localhost:8080/camunda/app/tasklist/default/#/)) and start a process instance of the *Example Process* | ||
7. Modify the process model ([src/main/resources/exampleProcess.bpmn](src/main/resources/exampleProcess.bpmn)), for example by adding another user task | ||
8. Build it again with maven | ||
9. Re-deploy the web application | ||
10. Go to Camunda Cockpit ([http://localhost:8080/camunda/app/cockpit/default/#/](http://localhost:8080/camunda/app/cockpit/default/#/)) and see that the process instance was migrated | ||
|
||
How Does It Work? | ||
----------------- | ||
|
||
The process application class [ExampleProcessApplication](src/main/java/org/camunda/bpm/platform/example/migration/ExampleProcessApplication.java) declares a `@PostDeploy` hook. This is called whenever the application is deployed. | ||
The implementation then identifies the latest versions of the deployed process definitions. The migration from the second-latest to the latest version | ||
is implemented in `#migrateInstancesFromPreviousVersion`: | ||
|
||
```java | ||
protected void migrateInstances(ProcessEngine processEngine, | ||
ProcessDefinition sourceDefinition, | ||
ProcessDefinition targetDefinition) { | ||
|
||
RuntimeService runtimeService = processEngine.getRuntimeService(); | ||
|
||
MigrationPlan migrationPlan = runtimeService | ||
.createMigrationPlan(sourceDefinition.getId(), targetDefinition.getId()) | ||
.mapEqualActivities() | ||
.build(); | ||
|
||
LOG.info("Migrating all process instances from " + sourceDefinition.getId() + " to " + targetDefinition.getId()); | ||
|
||
runtimeService | ||
.newMigration(migrationPlan) | ||
.processInstanceQuery(runtimeService.createProcessInstanceQuery().processDefinitionId(sourceDefinition.getId())) | ||
.execute(); | ||
// .executeAsync() for asynchronous execution in a batch (useful for large numbers of instances) | ||
} | ||
``` | ||
|
||
What Else is There to Know? | ||
--------------------------- | ||
|
||
* The migration plan is generated by calling `#mapEqualActivities` on the migration plan builder. See the documentation on [migration plan generation](http://docs.camunda.org/manual/7.5/user-guide/process-engine/process-instance-migration/#generating-a-migration-plan) for which kind of instruction this method can generate. The example can be extended to explicitly provide instructions via `mapActivities` to support advanced use cases. Such instructions could be parsed from an accompanying deployment descriptor. | ||
* The implementation is not robust when the process instances to migrate are modified in parallel. Migration may fail with an `OptimisticLockingException` and roll back in such a case. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>org.camunda.bpm.example</groupId> | ||
<artifactId>camunda-example-migrate-on-deployment</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<packaging>war</packaging> | ||
|
||
<properties> | ||
<version.camunda>7.5.0</version.camunda> | ||
<failOnMissingWebXml>false</failOnMissingWebXml> | ||
</properties> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>org.camunda.bpm</groupId> | ||
<artifactId>camunda-bom</artifactId> | ||
<version>${version.camunda}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.camunda.bpm</groupId> | ||
<artifactId>camunda-engine</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>javax.servlet</groupId> | ||
<artifactId>javax.servlet-api</artifactId> | ||
<version>3.0.1</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<!-- redirect slf4j logging to jdk logging --> | ||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-jdk14</artifactId> | ||
<version>1.7.12</version> | ||
</dependency> | ||
</dependencies> | ||
</project> |
160 changes: 160 additions & 0 deletions
160
...t/src/main/java/org/camunda/bpm/platform/example/migration/ExampleProcessApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/* 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.platform.example.migration; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.logging.Logger; | ||
|
||
import org.camunda.bpm.BpmPlatform; | ||
import org.camunda.bpm.application.PostDeploy; | ||
import org.camunda.bpm.application.ProcessApplication; | ||
import org.camunda.bpm.application.ProcessApplicationDeploymentInfo; | ||
import org.camunda.bpm.application.ProcessApplicationInfo; | ||
import org.camunda.bpm.application.impl.ServletProcessApplication; | ||
import org.camunda.bpm.engine.ProcessEngine; | ||
import org.camunda.bpm.engine.RepositoryService; | ||
import org.camunda.bpm.engine.RuntimeService; | ||
import org.camunda.bpm.engine.migration.MigrationPlan; | ||
import org.camunda.bpm.engine.repository.ProcessDefinition; | ||
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery; | ||
|
||
@ProcessApplication | ||
public class ExampleProcessApplication extends ServletProcessApplication { | ||
|
||
private final static Logger LOG = Logger.getLogger(ExampleProcessApplication.class.getName()); | ||
|
||
/** | ||
* <p>Migrates all process instances from the last version of the process definitions | ||
* deployed by this application to the latest version. | ||
*/ | ||
@PostDeploy | ||
public void migrateProcessInstances(ProcessApplicationInfo processApplicationInfo) { | ||
|
||
Map<String, List<ProcessApplicationDeploymentInfo>> deploymentsByEngine = organizeDeploymentsByEngine(processApplicationInfo); | ||
|
||
for (String engineName : deploymentsByEngine.keySet()) { | ||
ProcessEngine processEngine = BpmPlatform.getProcessEngineService().getProcessEngine(engineName); | ||
|
||
List<ProcessDefinition> latestIncludedProcessDefinitions = | ||
collectLatestProcessDefinitions(processEngine, deploymentsByEngine.get(engineName)); | ||
|
||
for (ProcessDefinition latestDefinition : latestIncludedProcessDefinitions) { | ||
ProcessDefinition previousDefinition = getPreviousVersion(processEngine, latestDefinition); | ||
migrateInstances(processEngine, previousDefinition, latestDefinition); | ||
} | ||
} | ||
} | ||
|
||
protected Map<String, List<ProcessApplicationDeploymentInfo>> organizeDeploymentsByEngine(ProcessApplicationInfo processApplicationInfo) { | ||
Map<String, List<ProcessApplicationDeploymentInfo>> deploymentsByEngine = new HashMap<String, List<ProcessApplicationDeploymentInfo>>(); | ||
|
||
for (ProcessApplicationDeploymentInfo deployment : processApplicationInfo.getDeploymentInfo()) { | ||
addToMapOfLists(deploymentsByEngine, deployment.getProcessEngineName(), deployment); | ||
} | ||
|
||
return deploymentsByEngine; | ||
} | ||
|
||
|
||
protected List<ProcessDefinition> collectLatestProcessDefinitions(ProcessEngine processEngine, List<ProcessApplicationDeploymentInfo> list) { | ||
List<ProcessDefinition> latestDefinitions = processEngine | ||
.getRepositoryService() | ||
.createProcessDefinitionQuery() | ||
.latestVersion() | ||
.list(); | ||
|
||
Set<String> deploymentIds = collectDeploymentIds(list); | ||
|
||
List<ProcessDefinition> latestDefinitionsForApplication = new ArrayList<ProcessDefinition>(); | ||
|
||
for (ProcessDefinition latestDefinition : latestDefinitions) { | ||
if (deploymentIds.contains(latestDefinition.getDeploymentId())) { | ||
latestDefinitionsForApplication.add(latestDefinition); | ||
} | ||
} | ||
|
||
return latestDefinitionsForApplication; | ||
} | ||
|
||
protected Set<String> collectDeploymentIds(List<ProcessApplicationDeploymentInfo> deploymentInfos) { | ||
Set<String> deploymentIds = new HashSet<String>(); | ||
|
||
for (ProcessApplicationDeploymentInfo deploymentInfo : deploymentInfos) { | ||
deploymentIds.add(deploymentInfo.getDeploymentId()); | ||
} | ||
|
||
return deploymentIds; | ||
} | ||
|
||
/** | ||
* Selects the process definition with the preceding version for the same key and tenant as the argument. | ||
*/ | ||
protected ProcessDefinition getPreviousVersion(ProcessEngine processEngine, ProcessDefinition processDefinition) { | ||
|
||
if (processDefinition.getVersion() <= 1) { | ||
return null; | ||
} | ||
|
||
RepositoryService repositoryService = processEngine.getRepositoryService(); | ||
|
||
ProcessDefinitionQuery processDefinitionQuery = repositoryService | ||
.createProcessDefinitionQuery() | ||
.processDefinitionKey(processDefinition.getKey()) | ||
.processDefinitionVersion(processDefinition.getVersion() - 1); | ||
|
||
if (processDefinition.getTenantId() != null) { | ||
processDefinitionQuery.tenantIdIn(processDefinition.getTenantId()); | ||
} | ||
else { | ||
processDefinitionQuery.withoutTenantId(); | ||
} | ||
|
||
return processDefinitionQuery.singleResult(); | ||
} | ||
|
||
protected void migrateInstances(ProcessEngine processEngine, | ||
ProcessDefinition sourceDefinition, | ||
ProcessDefinition targetDefinition) { | ||
|
||
RuntimeService runtimeService = processEngine.getRuntimeService(); | ||
|
||
MigrationPlan migrationPlan = runtimeService | ||
.createMigrationPlan(sourceDefinition.getId(), targetDefinition.getId()) | ||
.mapEqualActivities() | ||
.build(); | ||
|
||
LOG.info("Migrating all process instances from " + sourceDefinition.getId() + " to " + targetDefinition.getId()); | ||
|
||
runtimeService | ||
.newMigration(migrationPlan) | ||
.processInstanceQuery(runtimeService.createProcessInstanceQuery().processDefinitionId(sourceDefinition.getId())) | ||
.execute(); | ||
// .executeAsync() for asynchronous execution in a batch (useful for large numbers of instances) | ||
} | ||
|
||
|
||
protected static <S, T> void addToMapOfLists(Map<S, List<T>> map, S key, T value) { | ||
List<T> list = map.get(key); | ||
if (list == null) { | ||
list = new ArrayList<T>(); | ||
map.put(key, list); | ||
} | ||
list.add(value); | ||
} | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
migration/migrate-on-deployment/src/main/resources/META-INF/processes.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
|
||
<process-application | ||
xmlns="http://www.camunda.org/schema/1.0/ProcessApplication" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
|
||
<process-archive> | ||
<process-engine>default</process-engine> | ||
<properties> | ||
<property name="isDeleteUponUndeploy">false</property> | ||
<property name="isScanForProcessDefinitions">true</property> | ||
</properties> | ||
</process-archive> | ||
</process-application> |
41 changes: 41 additions & 0 deletions
41
migration/migrate-on-deployment/src/main/resources/exampleProcess.bpmn
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="_4TWVALQtEeOiRMg5tufUgQ" targetNamespace="http://camunda.org/examples" exporter="Camunda Modeler" exporterVersion="1.0.0" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"> | ||
<bpmn2:process id="exampleProcess" name="Example Process" isExecutable="true"> | ||
<bpmn2:startEvent id="StartEvent_1" camunda:initiator="initiator"> | ||
<bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing> | ||
</bpmn2:startEvent> | ||
<bpmn2:sequenceFlow id="SequenceFlow_1" name="" sourceRef="StartEvent_1" targetRef="UserTask_14494ua" /> | ||
<bpmn2:endEvent id="EndEvent_1"> | ||
<bpmn2:incoming>SequenceFlow_2</bpmn2:incoming> | ||
</bpmn2:endEvent> | ||
<bpmn2:sequenceFlow id="SequenceFlow_2" name="" sourceRef="UserTask_14494ua" targetRef="EndEvent_1" /> | ||
<bpmn2:userTask id="UserTask_14494ua" name="Example User Task" camunda:assignee="${initiator}"> | ||
<bpmn2:incoming>SequenceFlow_1</bpmn2:incoming> | ||
<bpmn2:outgoing>SequenceFlow_2</bpmn2:outgoing> | ||
</bpmn2:userTask> | ||
</bpmn2:process> | ||
<bpmndi:BPMNDiagram id="BPMNDiagram_1"> | ||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="exampleProcess"> | ||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1"> | ||
<dc:Bounds x="201" y="240" width="36" height="36" /> | ||
</bpmndi:BPMNShape> | ||
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow_1" sourceElement="_BPMNShape_StartEvent_2" targetElement="_BPMNShape_ServiceTask_2"> | ||
<di:waypoint xsi:type="dc:Point" x="237" y="258" /> | ||
<di:waypoint xsi:type="dc:Point" x="287" y="258" /> | ||
</bpmndi:BPMNEdge> | ||
<bpmndi:BPMNShape id="_BPMNShape_EndEvent_2" bpmnElement="EndEvent_1"> | ||
<dc:Bounds x="437" y="240" width="36" height="36" /> | ||
<bpmndi:BPMNLabel> | ||
<dc:Bounds x="410" y="276" width="90" height="20" /> | ||
</bpmndi:BPMNLabel> | ||
</bpmndi:BPMNShape> | ||
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_2" bpmnElement="SequenceFlow_2" sourceElement="_BPMNShape_ServiceTask_2" targetElement="_BPMNShape_EndEvent_2"> | ||
<di:waypoint xsi:type="dc:Point" x="387" y="258" /> | ||
<di:waypoint xsi:type="dc:Point" x="437" y="258" /> | ||
</bpmndi:BPMNEdge> | ||
<bpmndi:BPMNShape id="UserTask_14494ua_di" bpmnElement="UserTask_14494ua"> | ||
<dc:Bounds x="287" y="218" width="100" height="80" /> | ||
</bpmndi:BPMNShape> | ||
</bpmndi:BPMNPlane> | ||
</bpmndi:BPMNDiagram> | ||
</bpmn2:definitions> |