Skip to content

Commit

Permalink
feat(examples): migrate process instances when deploying a new version
Browse files Browse the repository at this point in the history
related to CAM-5774
  • Loading branch information
ThorbenLindhauer authored and meyerdan committed May 31, 2016
1 parent 5abfca1 commit 3447ee7
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ If you clone this repository, use the checkout commands to access the sources fo
* [Multi-Tenancy](#multi-tenancy-examples)
* [Spin](#spin-examples)
* [DMN](#dmn-examples)
* [Process Instance Migration](#process-instance-migration-examples)

### Getting Started with camunda BPM

Expand Down Expand Up @@ -156,6 +157,14 @@ If you clone this repository, use the checkout commands to access the sources fo
| [Embed Decision Engine - Dish Decision Maker](dmn-engine/dmn-engine-java-main-method) | Jar | DMN, Embed |


### Process Instance Migration Examples

| Name | Container | Keywords |
| ---------------------------------------------------------------------------------------------|---------------------------------------|-------------------------|
| [Migration on Deployment of New Process Version](migration/migrate-on-deployment) | Application Server with Shared Engine | BPMN, Migration |



### Contribute!

* Website: http://www.camunda.org/
Expand Down
53 changes: 53 additions & 0 deletions migration/migrate-on-deployment/README.md
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.
49 changes: 49 additions & 0 deletions migration/migrate-on-deployment/pom.xml
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>
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);
}

}
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>
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>

0 comments on commit 3447ee7

Please sign in to comment.