Process Instance Migration does not migrate task form keys and task name variables

Hello,

I am using Flowable 6.6.0 java API and I am trying to implement process instances migration logic.
I have processes with UserTasks that have assigned form keys.
When I execute migrate method on ProcessInstanceMigrationBuilder, with ActivityMigrationMappings, I have seen than UserTask key and name were changed, but the form key assigned to activity was not changed.
This is a problem for me because i see the correct task in my task inbox but when I open the task, I see the old form fields.
I have a similar problem if the new task name has variables (p.e “My task ${var1}”). The var1 value is not rendered because it is not evaluated in the migration (I am using withProcessInstanceVariables method on migration).

Is there any way to resolve these migration problems?

Thanks in advance.

For the form key: that sounds strange - if you migrate to a new process definition, those things come from the lastest definition. You’re using simply formKey=“XYZ”?

For the task - yes, that could be a problem, task names are not re-evaluated on migrated (which is what you want).

Maybe an example could help to explain what is the problem with form keys…

I have a process instances of “Process1” (version 1) with following definition:

Start → UserTaskA (formKey:taskA) → UserTaskB (formKey:taskB) → UserTaskC (formKey:taskC) → End

I have an instance waiting in UserTaskB.

Now I want deploy “Process1” (version 2) with following definition:

Start → UserTaskA (formKey:taskA) → UserTaskC (formKey:taskC) → End

In process version 2, UserTaskB doesn’t exist anymore so I do the migration with mapping UserTaskB to UserTaskA because I want that all old processes that were waiting in UserTaskB have to complete UserTaskA again.

When migration finishes, my existing instance is waiting in UserTaskA (that’s OK) but its form key is taskB, so I see in inbox a UserTaskA waiting the data defined in taskB form (because on migration, flowable only changes task key and task name).

I hope my explanation help to understand what my problem is.

Thanks in advance.

Thanks for the explanation, that seems to be a bug. We’ll work on a fix.
In the meantime, you could ‘fix up’ the related task in a custom piece of Java using the .postUpgradeJavaDelegate() method.

Fix comitted: Improve process migration: task form key / owner / category · flowable/flowable-engine@da95eab · GitHub

Thanks for your quickly and fantastic work!
We will try to integrate your fix in our project and see how we can resolve listeners executions and task name expression evaluation.

Thanks again.

Hello again @joram ,

We have tested your fix in our project and it works OK, but we have found a similar problem with executions listeners and tasks listeners.
We are trying re-execute the listeners (start and create events respectively) using post migration processor as you recommended us, but the DelegateExecution parameter passed to execute method presents different currentActivityId (taskA) and currentFlowElementId (taskB).
If we use currentFlowElement to get the listeners, we get taskB listeners. The only way that we have found to get taskA listeners (the listeners that we want to re-execute), has been getting the BpmnModel, getting the “taskA” flowElement (using the currentActivityId) and execute its listeners.

This makes us unable to use the ListenerNotificationHelper (because it uses currentFlowElement) and implement a similar code using the listeners obtained by BpmnModel.

This is the code of our post migration processor:

	@Override
	public void execute(DelegateExecution execution) {
		execution.getExecutions().forEach(exe -> {
			
			String currentActivityId = exe.getCurrentActivityId();
			String currentFlowElementId = exe.getCurrentFlowElement().getId();
			
			if (!currentActivityId.equals(currentFlowElementId)) {
				//changed!
				log.info(String.format("OLD: %s, NEW: %s",currentFlowElementId, currentActivityId));
				
				ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
						.processDefinitionId(exe.getProcessDefinitionId())
						.singleResult();
				
				try {
					InputStream isModel = repositoryService.getProcessModel(definition.getId());
			        XMLInputFactory xif = XMLInputFactory.newInstance();
					XMLStreamReader xtr = xif.createXMLStreamReader(isModel);
					BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
					
					FlowElement flowElement = bpmnModel.getFlowElement(currentActivityId);
					
	            	executeListeners(flowElement.getExecutionListeners(), ExecutionListener.EVENTNAME_START, exe);
		            
		            if (flowElement instanceof UserTask) {
		            	executeTaskListeners(((UserTask)flowElement).getTaskListeners(),TaskListener.EVENTNAME_CREATE, exe);
		            }
				}
				catch(Exception e) {
					e.printStackTrace();
				}
			}
		});
		
	}
	
	private void executeListeners(List<FlowableListener> listeners, String eventType, DelegateExecution execution) {
		if (listeners == null) {
			return;
		}
		
		final ListenerFactory listenerFactory = CommandContextUtil.getProcessEngineConfiguration().getListenerFactory();
		
        listeners.forEach(listener -> {

			if (listener.getEvent().equals(eventType)) {
				
				BaseExecutionListener executionListener = null;
	
	            if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(listener.getImplementationType())) {
	                executionListener = listenerFactory.createClassDelegateExecutionListener(listener);
	            } else if (ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION.equalsIgnoreCase(listener.getImplementationType())) {
	                executionListener = listenerFactory.createExpressionExecutionListener(listener);
	            } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(listener.getImplementationType())) {
	                if (listener.getOnTransaction() != null) {
	                    executionListener = listenerFactory.createTransactionDependentDelegateExpressionExecutionListener(listener);
	                } else {
	                    executionListener = listenerFactory.createDelegateExpressionExecutionListener(listener);
	                }
	            } else if (ImplementationType.IMPLEMENTATION_TYPE_INSTANCE.equalsIgnoreCase(listener.getImplementationType())) {
	                executionListener = (ExecutionListener) listener.getInstance();
	            }
	            
	            ((ExecutionListener) executionListener).notify(execution);
			}
        });
	}
	
	
	private void executeTaskListeners(List<FlowableListener> listeners, String eventType, DelegateExecution taskExecution) {
		if (listeners == null) {
			return;
		}
		
		final ListenerFactory listenerFactory = CommandContextUtil.getProcessEngineConfiguration().getListenerFactory();

		TaskEntity taskEntity = CommandContextUtil.getTaskService().findTasksByExecutionId(taskExecution.getId()).get(0);
		
        listeners.forEach(listener -> {

			if (listener.getEvent().equals(eventType)) {
				
				BaseTaskListener taskListener = null;

		        if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(listener.getImplementationType())) {
		            taskListener = listenerFactory.createClassDelegateTaskListener(listener);
		        } else if (ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION.equalsIgnoreCase(listener.getImplementationType())) {
		            taskListener = listenerFactory.createExpressionTaskListener(listener);
		        } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(listener.getImplementationType())) {
		            if (listener.getOnTransaction() != null) {
		                taskListener = listenerFactory.createTransactionDependentDelegateExpressionTaskListener(listener);
		            } else {
		                taskListener = listenerFactory.createDelegateExpressionTaskListener(listener);
		            }
		        } else if (ImplementationType.IMPLEMENTATION_TYPE_INSTANCE.equalsIgnoreCase(listener.getImplementationType())) {
		            taskListener = (TaskListener) listener.getInstance();
		        }
		        
		        ((TaskListener)taskListener).notify(taskEntity);
			}
        });
	}		
	

But this “solution” has many problems with the database transaction, error handling… because the process instance is migrated even if its listeners execution fails.