0
votes

in order of developing a custom BPM Application there is one feature we used with another BPM engine provider and like to use it with camunda too. The targeted functionality is about setting/reset running process instances to a specified task other than the current active one. From our perspective necessary when e.g.:

  • authoring process-instances due to process-version migration
  • resolving incidents
  • resolving accidentially wrong usage by an user

Finally I didn't really found a simple function to do this but worked out some custom code which worked with some limitations. There are some weaknesses and uncertainities within this code so that I have the following question:

Did I miss an alternative way to achieve this or is the following approach correct or is it even fully unsupported at the moment ?

The current weaknesses imho:

  • first and most important: no historic task instance is stored. This causes that it's not traceable who or even when the task was triggered/activate/started. I found the following post on camunda google group (post) which says that it's correct at this point because it’s a task out of the process definition scope but by using a task definition from the underlying process definition I should be "in scope" ?!
  • the code is based on internal implementation and not on official interface
  • at this point a lot of "bootstrap"/initialization have to be done manually but as user (not developer of camunda) I am not fully aware of what is required and what is optional
  • some parts like parsing expressions from task definition failed (see code commented out) but that may be caused by wrong usage

Here's the code (experimental snippet of our camunda service facade) :

@Inject
protected HistoryService histService;
@Inject
protected TaskService taskService;
@Inject
protected ManagementService managementService;
@Inject
protected RuntimeService runtimeService;
@Inject
protected IdentityService identityService;
@Inject
protected RepositoryService repositoryService;
@Inject
protected FormService formService;
@Inject
protected ProcessEngine processEngine;


public void startTask(String processInstanceId, String taskKey) {
    Collection<TaskDefinition> taskDefs = getAvailableTasks(
            processInstanceId);
    TaskEntity newTask = null;
    TaskDefinition taskDef = null;
    for (TaskDefinition taskDefinition : taskDefs) {
        if (taskDefinition.getKey().equals(taskKey)) {
            taskDef = taskDefinition;
            break;
        }
    }
    boolean taskDefExists = taskDef != null;
    List<Task> runningTasksByKey = getTasksByKey(taskKey, processInstanceId);
    boolean taskIsAlreadyRunning = runningTasksByKey != null
            && runningTasksByKey.size() > 0;
    if (taskDefExists && !taskIsAlreadyRunning) {
        newTask = (TaskEntity) taskService.newTask();
        ProcessInstance procInst = getProcessInstance(processInstanceId);
        ExecutionEntity procInstEntity = (ExecutionEntity) procInst;
        String taskName = (String) taskDef.getNameExpression().
                getExpressionText();
//            String taskAssigne = (String) taskDef.getAssigneeExpression().
//                    getValue(
//                            procInstEntity);
//            newTask.setAssignee(taskAssigne);
            newTask.setTaskDefinitionKey(taskDef.getKey());
            newTask.setProcessInstance(procInstEntity);
            newTask.setTaskDefinition(taskDef);
            newTask.setName(taskName);
            newTask.setProcessInstanceId(processInstanceId);
            newTask.setProcessDefinitionId(procInstEntity.
                    getProcessDefinitionId());
            taskService.saveTask(newTask);

        TaskServiceImpl taskServiceImpl = (TaskServiceImpl) BpmPlatform.
                getProcessEngineService().getDefaultProcessEngine().
                getTaskService();
        CommandExecutor commandExecutor = taskServiceImpl.
                getCommandExecutor();
        ExecutionEntity executionEntity = commandExecutor.execute(
                new SaveTaskActivityInstanceCmd(newTask,
                        procInstEntity));
//            commandExecutor.execute(new `SaveTaskHistoricActivityInstanceCmd(executionEntity, newTask));`
    }
}

public Collection<TaskDefinition> getAvailableTasks(String processInstanceId) {
            Map<String, TaskDefinition> taskDefs = null;
            Collection<TaskDefinition> taskDefObjects = null;
            if (processInstanceId != null) {
                ProcessInstanceQuery procInstQuery = runtimeService.
                        createProcessInstanceQuery().processInstanceId(
                                processInstanceId);
                ProcessDefinitionEntity procDefEntity = getProcessDefinitionEager(
                        processInstanceId);
                taskDefs = procDefEntity.getTaskDefinitions();
            }
            taskDefObjects = (Collection<TaskDefinition>) (taskDefs != null ? taskDefs.
                    values() : new ArrayList<TaskDefinition>());
            return taskDefObjects;
}

public ProcessDefinitionEntity getProcessDefinitionEager(
        String processInstanceId) {
    ProcessInstanceQuery procInstQuery = runtimeService.
            createProcessInstanceQuery().processInstanceId(
                    processInstanceId);
    ProcessInstance procInst = procInstQuery.singleResult();
    String procDefId = procInst.getProcessDefinitionId();
    return (ProcessDefinitionEntity) repositoryService.getProcessDefinition(
            procDefId);
}

public List<Task> getTasksByKey(String taskKey, String processInstanceId) {
    List<Task> tasks = taskService.createTaskQuery().processInstanceId(
            processInstanceId).taskDefinitionKey(taskKey).list();
    return tasks;
}


public class SaveTaskActivityInstanceCmd implements Command<ExecutionEntity>,  
      Serializable {

    private TaskEntity newTask;
    private ExecutionEntity procInstEntity;

    public SaveTaskActivityInstanceCmd(TaskEntity newTaskInit,
            ExecutionEntity procInstEntityInit) {
        this.newTask = newTaskInit;
        this.procInstEntity = procInstEntityInit;
    }


    public ExecutionEntity execute(CommandContext commandContext) {
          ActivityImpl actImpl = new ActivityImpl(newTask.
                getTaskDefinitionKey(),
                procInstEntity.getProcessDefinition());
          actImpl.setActivityBehavior(new UserTaskActivityBehavior(
                new CdiExpressionManager(), newTask.getTaskDefinition()));
          ExecutionEntity execEntity = new ExecutionEntity();
          execEntity.setActivity(actImpl);
          execEntity.setActivityInstanceId(newTask.getTaskDefinitionKey()
                + ":" + newTask.getId());
          execEntity.setEventName(newTask.getEventName());
          execEntity.setProcessDefinitionId(newTask.getProcessDefinitionId());
          execEntity.setActive(true);
          execEntity.setProcessInstance(procInstEntity);
          commandContext.getExecutionManager().insert(execEntity);
          return execEntity;
     }
}

I appreciate any hint or advice :-)

2
Could you provide your bpmn file? Seems to me like you are doing a lot of hacks either because a) your model does not fit your needs or b) your project is no bpm project (maybe acm?) – Jan Galinski
Well, from my point of view it doesn't matter what the bpmn looks like because we want to use this feature on any bpmn. For example : given simple process definition : Start -> Usertask A -> Usertask B -> End In case of incoming new requirements we need a new service task: Start -> Usertask A -> Servicetask -> Usertask B -> End Now we have to reset running process instances where the token is already on usertask B to usertask A so that this instances can run through the new servicetask. – user3895072
Ok, I see ... you are deploying extended versions of your process and need to migrate running process instances. From what I read above, I assumed you wanted to change the behavior of the running instance. Question 1: do you have processes that run so long that its worth to migrate? Why not let the instance finish with the old version? Question 2: do you really want the user to repeat his work in Task A? Wouldn't you want to reset the instance to right after the task? – Jan Galinski

2 Answers

4
votes

Beginning with Camunda 7.3, you can use process instance modification to start any activity in a process and cancel any active activity instance.

Example:

runtimeService.createProcessInstanceModification(processInstanceId)
  .startBeforeActivity("someActivityId")
  .cancelActivityInstance("someActivityInstanceId")
  .execute();

See http://docs.camunda.org/7.3/guides/user-guide/#process-engine-process-instance-modification for documentation.

2
votes

I wouldn't mess with the process instance on that level, as you already noticed, you are bypassing camundas services. When faced with a similar problem, we went with the following:

  1. cancel the process instance of the old process version
  2. start a new instance of the extended process and forward it programmatically to the desired state ...

Another option: model an entry point (message start event) inside the new process version. Then, instead of programmatically forwarding the instance to the desired state, just start the new instance via the event and pass all process variables of the old instance ...