Migration Plan for removed ServiceTask


#1

Hello,

I removed a ServiceTask from a process definition,
but when I define a MigrationPlan to map equal activities an exception is thrown.
“There is no migration instruction for this instance’s activity”

The definition was …—>UserTask—>ServiceTask—>IntermediateThrowEvent—>EndEvent.

Now it is …—>UserTask—>EndEvent.

The applied migration Plan is the simplest possible.
runtimeService.createMigrationPlan(current.getId(),latest.getId()).mapEqualActivities().build();

The ServiceTask can’t be mapped to the IntermediateThrowEvent or EndEvent.

Is there anything I could do to sucessfully migrate this definition?

The Problem even occurs if I leave the definition as is but change the implementation
of the ServiceTask from DelegateExpression to Expression.
That however is because
“Activities have incompatible types (ServiceTaskDelegateExpressionActivityBehavior is not compatible with ServiceTaskExpressionActivityBehavior)”

Best wishes
hlux


#2

Hi,

Could you please provide an executable test case? From your description, the scenario in which the exception occurs is not totally clear to me. You can use the test case template to get started.

Thanks,
Thorben


#3

Hi,

thank you very much for your reply.
I investigated a bit further on the issue and the “problem” seems to be that the service task
is configured to be “asyncBefore” and “asyncAfter”. It appears that a successful migration is depending
on the state of the process, that is to be migrated.
If it is waiting at an async service task the migration fails.

Please find attached the extended test case which demonstrates the behaviour.
Project with test case (will be deleted at 18.07.2017)

I hope this clarifies things.

cheers,
hlux

A preview of the test case:

package org.camunda.bpm.unittest;

import java.util.List;

import org.camunda.bpm.engine.migration.MigrationPlan;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceQuery;
import org.camunda.bpm.engine.test.ProcessEngineRule;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;

import static org.camunda.bpm.engine.test.assertions.ProcessEngineAssertions.assertThat;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.complete;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.execute;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.job;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.repositoryService;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.runtimeService;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.task;

public class MigrationTest
{

    private static final String FEED_FISH_PROCESS = "FeedFishProcess";

    @AfterClass
    public static void cleanUp()
    {
        repositoryService().createDeploymentQuery()
                           .deploymentName("feedfish")
                           .list()
                           .forEach(d -> repositoryService().deleteDeployment(
                                   d.getId(), true));

    }

    @Rule
    public ProcessEngineRule rule = new ProcessEngineRule();

    @Test
    public void testModelMigration()
    {
        deployVersionOne();

        final ProcessInstance processOne = startProcess();
        final ProcessInstance processTwo = startProcess();

        final List<ProcessDefinition> currentProcessDefinitions = getDeployedProcessDefinitions();

        deployVersionTwo();

        assertsBeforeMigration(processOne);

        final ProcessDefinition latestProcessDefinition = getLatestDeployedProcessDefinition();

        migrateAllCurrentDefinitionsToLatest(currentProcessDefinitions,
                latestProcessDefinition);

        assertsAfterMigration(processOne, processTwo);

    }

    /**
     * Asserts after successful migration.
     *
     * @param processOne
     * @param processTwo
     */
    private void assertsAfterMigration(final ProcessInstance processOne,
            final ProcessInstance processTwo)
    {

        assertThat(processOne).isWaitingAt("CheckFoodSupply");
        assertThat(processTwo).isStarted()
                              .isNotEnded();

        assertThat(processTwo).isWaitingAt("FeedTheFish");
        complete(task(processTwo));
        assertThat(processTwo).isWaitingAt("CleanAquarium");
    }

    /**
     * Asserts before migration
     *
     * @param processOne
     */
    private void assertsBeforeMigration(final ProcessInstance processOne)
    {
        assertThat(processOne).isStarted()
                              .isNotEnded();

        assertThat(processOne).isWaitingAt("FeedTheFish");
        complete(task(processOne));
        assertThat(processOne).isWaitingAt("ServiceTask");
        execute(job());

        // TODO Uncomment the next line to make the test pass.
        // execute(job());
        // assertThat(processOne).isWaitingAt("CheckFoodSupply");
    }

    /**
     * Creates a simple migration plan, that maps equal activities
     *
     * @param processDefinitionLatest
     * @param current
     * @return
     */
    private MigrationPlan createSimpleMigrationPlan(ProcessDefinition current,
            ProcessDefinition processDefinitionLatest)
    {
        System.out.println("latest :" + processDefinitionLatest);
        return runtimeService().createMigrationPlan(current.getId(),
                processDefinitionLatest.getId())
                               .mapEqualActivities()
                               .build();
    }

    /**
     * Deploys the given {@link BpmnModelInstance}
     *
     * @param modelInstance
     */
    private void deployModel(BpmnModelInstance modelInstance)
    {
        repositoryService().createDeployment()
                           .addModelInstance("feedFish.bpmn", modelInstance)
                           .name("feedfish")
                           .deploy();

    }

    /**
     * Deploys version one of the process definition
     */
    private void deployVersionOne()
    {

        deployModel(getModelVersionOne());
    }

    /**
     * Deploys version two of the process definition
     */
    private void deployVersionTwo()
    {

        deployModel(getModelVersionTwo());
    }

    /**
     * Executes the given migration plan.
     *
     * @param migrationPlan
     */
    private void executeMigrationPlan(MigrationPlan migrationPlan)
    {
        final ProcessInstanceQuery processInstanceQuery = runtimeService().createProcessInstanceQuery()
                                                                          .processDefinitionId(
                                                                                  migrationPlan.getSourceProcessDefinitionId());
        runtimeService().newMigration(migrationPlan)
                        .processInstanceQuery(processInstanceQuery)
                        .execute();
    }

    /**
     * Returns the deployed process definitions.
     *
     * @return
     */
    private List<ProcessDefinition> getDeployedProcessDefinitions()
    {
        return repositoryService().createProcessDefinitionQuery()
                                  .processDefinitionKey(FEED_FISH_PROCESS)
                                  .list();
    }

    /**
     * Returns the last version of the process definition.
     *
     * @return
     */
    private ProcessDefinition getLatestDeployedProcessDefinition()
    {
        return repositoryService().createProcessDefinitionQuery()
                                  .processDefinitionKey(FEED_FISH_PROCESS)
                                  .latestVersion()
                                  .singleResult();
    }

    /**
     * Creates a {@link BpmnModelInstance} of version one of the process
     * definition and returns it.
     *
     * @return
     */
    private BpmnModelInstance getModelVersionOne()
    {
        final BpmnModelInstance modelInstance = Bpmn.createExecutableProcess(
                FEED_FISH_PROCESS)
                                                    .startEvent("StartProcess")
                                                    .userTask("FeedTheFish")
                                                    .serviceTask("ServiceTask")
                                                    .camundaDelegateExpression(
                                                            "${serviceTaskDelegate}")
                                                    .camundaAsyncBefore()
                                                    .camundaAsyncAfter()
                                                    .userTask("CheckFoodSupply")
                                                    .endEvent("EndProcess")
                                                    .done();
        return modelInstance;
    }

    /**
     * Creates a {@link BpmnModelInstance} of version two of the process
     * definition and returns it.
     *
     * @return
     */
    private BpmnModelInstance getModelVersionTwo()
    {
        final BpmnModelInstance modelInstance = Bpmn.createExecutableProcess(
                FEED_FISH_PROCESS)
                                                    .startEvent("StartProcess")
                                                    .userTask("FeedTheFish")
                                                    .userTask("CleanAquarium")
                                                    .userTask("CheckFoodSupply")
                                                    .endEvent("EndProcess")
                                                    .done();
        return modelInstance;
    }

    /**
     * Migrates all existing processes to the latest version of the process
     * definition.
     *
     *
     * @param currentProcessDefinitions
     * @param latestProcessDefinition
     */
    private void migrateAllCurrentDefinitionsToLatest(
            final List<ProcessDefinition> currentProcessDefinitions,
            final ProcessDefinition latestProcessDefinition)
    {
        currentProcessDefinitions.stream()
                                 .map(current -> {

                                     return createSimpleMigrationPlan(current,
                                             latestProcessDefinition);
                                 })
                                 .forEach(migrationPlan -> {

                                     executeMigrationPlan(migrationPlan);

                                 });
    }

    /**
     * Starts a process and returns it.
     *
     * @return
     */
    private ProcessInstance startProcess()
    {
        return runtimeService().startProcessInstanceByKey(FEED_FISH_PROCESS);
    }
}

#4

Hi hlux,

mapEqualActivities expects to have a matching (same ID, same type) task in the destination definition.

When the ID is different, but the type is the same, then you have to use mapActivities(sourceActivityId,targetActivityId) instead.
When mapping different types, you can’t map it and have to use the process modification API additionally.

Maybe you want to have a look at this extension http://github.com/camunda/camunda-bpm-migration
It has currently a feature in develoment that addresses the problem when mapping different task types.

If you have questions regarding this extensions, feel free to ask, I’ll be glad to help.

Cheers,
Malte


#5

Hi Malte,

thank you very much for tour reply.
I will have a look at that extension and see where I can get with it and with
your suggestion of using the modification API.

best wishes,
hlux