Activiti7 Event Listening

Personal blog address for this article: Activiti7 Event Listening (leafage.top)

I haven't taken notes for a long time. Recently, I've done some things about workflow and recorded some experience using activiti 7.

Requirements:

  1. Send notification reminders for process approval to related personnel during process initiation and process operation.
  2. Don't add it manually when configuring a process. It can't intrude into the process of process operation and affect process execution.

How do I get started with this? Without activiti, the official document of activiti7 is as bad as shit. It feels so hard. 😔…

Documentation reference is not high, then try the official example, find activiti repository, there is an example module called activiti-examples, the example here can not run directly, can only see, to run, copy, paste, put in their own project. Got off the topic and talked about it.

Several important associated classes or interfaces in activiti:

  1. Each process information in activiti is described by ProcessInstance and has several states: created, started, completed, cancelled, resumed, updated, suspended. The corresponding event description classes are ProcessCreatedEvent, ProcessStartedEvent, ProcessCompletedEvent, ProcessCancelledEvent, ProcessResumedEvent, ProcessUpdatedEvent, and ProcessSuspendedEvent, etc.

  2. Each process node is described by a Task in activiti and has several states: created, assigned, completed, updated, cancelled, suspended, etc. The corresponding event description classes are TaskCreatedEvent, TaskAssignedEvent, TaskCompletedEvent, TaskUpdatedEvent, TaskCancelledEvent, TaskSuspendedEvent, and so on.

How do I configure the listener?

1. Global Event Listener:

Two classes\interfaces are involved, the global event listener ActivitiEventListener and the Process EngineConfiguration Configurer (with a default implementation class: DefaultActivityBehaviorFactoryMappingConfigurer)

The ActitiviEventListener interface has a void onEvent(ActivitiEvent activitiEvent) method in which actions that can occur when the state of an event changes. The source code is as follows:

/**
 * Describes a class that listens for {@link ActivitiEvent}s dispatched by the engine.
 * 

 */
public interface ActivitiEventListener {

  void onEvent(ActivitiEvent event);

  boolean isFailOnException();
}

ActivitiEvent contains the process definition ID, sample ID, execution ID, and event type information from the following sources:

public interface ActivitiEvent {

  ActivitiEventType getType();

  String getExecutionId();

  String getProcessInstanceId();

  String getProcessDefinitionId();
}

There are many types of events, and the source code is as follows:

public enum ActivitiEventType {

  // ENTITY: A process instance, created from a process template when a process is initiated
  ENTITY_CREATED,  // Establish

  ENTITY_INITIALIZED,  // Initialization complete (if the creation of this entity will include the creation of subentities, this event will be triggered after the creation/initialization of all subentities is complete, which is different from ENTITY_CREATED)

  ENTITY_UPDATED,  // To update

  ENTITY_DELETED,  // delete

  ENTITY_SUSPENDED,  // Pause (thrown by ProcessDefinitions, ProcessInstances, and Tasks)

  ENTITY_ACTIVATED,  // Activation (thrown by ProcessDefinitions, ProcessInstances, and Tasks)

  // timer
  TIMER_SCHEDULED,  // Establish

  TIMER_FIRED,  // trigger

  // task
  JOB_CANCELED,  // cancel

  JOB_EXECUTION_SUCCESS,  // Successful execution

  JOB_EXECUTION_FAILURE,  // Failed to execute

  JOB_RETRIES_DECREMENTED,  // Fewer retries (fewer retries due to job execution failure)

  CUSTOM,  // custom
  
  // engine
  ENGINE_CREATED,  // Establish

  ENGINE_CLOSED,  // Close

  // Process Node
  ACTIVITY_STARTED,  // start

  ACTIVITY_COMPLETED,  // complete

  ACTIVITY_CANCELLED,  // cancel

  ACTIVITY_SIGNALED,  // A signal was received

  ACTIVITY_COMPENSATE,  // Will be compensated
  
  ACTIVITY_MESSAGE_SENT,  // message sending
 
  ACTIVITY_MESSAGE_WAITING,  // Message Waiting

  ACTIVITY_MESSAGE_RECEIVED,  // messages receiving

  ACTIVITY_ERROR_RECEIVED,  // Receive Failure
  
  // Process History
  HISTORIC_ACTIVITY_INSTANCE_CREATED,  // Establish
  
  HISTORIC_ACTIVITY_INSTANCE_ENDED,  // End

  // Queue Flow
  SEQUENCEFLOW_TAKEN,  // Has been taken

  UNCAUGHT_BPMN_ERROR,  // No bpmn exception was obtained

  // variable
  VARIABLE_CREATED,  // Establish

  VARIABLE_UPDATED,  // To update

  VARIABLE_DELETED,  // delete

  // task
  TASK_CREATED,  // Created (after the ENTITY_CREATE event. When a task is created by a process, this event is executed before the TaskListener executes)

  TASK_ASSIGNED,  // distribution

  TASK_COMPLETED,  // Complete (it will be triggered before the ENTITY_DELETE event. When a task is part of a process, the event will be ACTIVITY_COMPLETE, corresponding to the node that completed the task, before the process continues to run)

  // process
  PROCESS_STARTED,  // start

  PROCESS_COMPLETED,  // Finish (Triggered after the ACTIVITY_COMPLETED event on the last node. The process ends when it reaches a state where there are no subsequent connections.)
 
  PROCESS_COMPLETED_WITH_ERROR_END_EVENT,  // Abnormal End

  PROCESS_CANCELLED,  // cancel

  HISTORIC_PROCESS_INSTANCE_CREATED,  // Process Instance Creation

  HISTORIC_PROCESS_INSTANCE_ENDED,  // Process Instance Creation

  // member
  MEMBERSHIP_CREATED,  // Users are added to a group

  MEMBERSHIP_DELETED,  // Users are deleted from a group

  MEMBERSHIPS_DELETED;  // All members are removed from a group
  
  // other code ...
}

The void configure (SpringProcessEngine Configuration springProcessEngine Configuration) method in ProcessEngineConfiguration Configurer adds a custom event listener that scopes the entire process. The source code is as follows:

public class DefaultActivityBehaviorFactoryMappingConfigurer implements ProcessEngineConfigurationConfigurer {

    private VariablesMappingProvider variablesMappingProvider;

    private ProcessVariablesInitiator processVariablesInitiator;
    
    private final EventSubscriptionPayloadMappingProvider eventSubscriptionPayloadMappingProvider;

    public DefaultActivityBehaviorFactoryMappingConfigurer(VariablesMappingProvider variablesMappingProvider,
                                                           ProcessVariablesInitiator processVariablesInitiator,
                                                           EventSubscriptionPayloadMappingProvider eventSubscriptionPayloadMappingProvider){
        this.variablesMappingProvider = variablesMappingProvider;
        this.processVariablesInitiator = processVariablesInitiator;
        this.eventSubscriptionPayloadMappingProvider = eventSubscriptionPayloadMappingProvider;
    }
    @Override
    public void configure(SpringProcessEngineConfiguration processEngineConfiguration){
        processEngineConfiguration.setEventSubscriptionPayloadMappingProvider(eventSubscriptionPayloadMappingProvider);

        processEngineConfiguration.setActivityBehaviorFactory(new MappingAwareActivityBehaviorFactory(variablesMappingProvider,
                                                                                                      processVariablesInitiator));
    }
}

How do I listen for events?

  1. Implement the ActivitiEventListener interface and override the void onEvent method;
  2. Add a new configuration class, inherit the DefaultActivityBehaviorFactoryMappingConfigurer class (or implement the ProcessEngineConfiguration Configurer interface) and override the configura() method to add a custom listener implementation class to the SpringProcessEngineConfiguration property;

Examples are as follows:

@Configuration
public class ActivitiConfiguration extends DefaultActivityBehaviorFactoryMappingConfigurer {

    @Autowired
    ActivitiTaskLogService activitiTaskLogService;

    @Autowired
    UserService userService;

    public ActivitiConfiguration(VariablesMappingProvider variablesMappingProvider, ProcessVariablesInitiator processVariablesInitiator,
                                 EventSubscriptionPayloadMappingProvider eventSubscriptionPayloadMappingProvider) {
        super(variablesMappingProvider, processVariablesInitiator, eventSubscriptionPayloadMappingProvider);
    }

    @Override
    public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {
        super.configure(springProcessEngineConfiguration);

        springProcessEngineConfiguration.setEventListeners(
                Collections.singletonList(new ActivitiTaskEventListener(activitiTaskLogService, userService)));
    }
}
public class ActivitiTaskEventListener implements ActivitiEventListener {

    @Override
    public void onEvent(ActivitiEvent activitiEvent) {
        if (!StringUtils.hasText(activitiEvent.getProcessInstanceId())) {
            return;
        }
        processEngine = ProcessEngines.getDefaultProcessEngine();

        // Process Instances
        ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery()
                .processInstanceId(activitiEvent.getProcessInstanceId()).singleResult();
        if (processInstance == null) {
            return;
        }

        List<ActivitiTaskLog> taskLogList = new ArrayList<>();
        switch (activitiEvent.getType()) {
            // After Task Execution
            case TASK_COMPLETED:
                // Task is processed, given to creator, candidate for next task
                List<ActivitiTaskLog> taskCompletedLogs = this.taskCompleted(processInstance, activitiEvent.getExecutionId());
                if (!CollectionUtils.isEmpty(taskCompletedLogs)) {
                    taskLogList.addAll(taskCompletedLogs);
                }
                break;
            case PROCESS_COMPLETED:
                // Process complete, notify creator, assistant, Copyer
                List<ActivitiTaskLog> processCompletedLogs = this.processCompleted(processInstance);
                if (!CollectionUtils.isEmpty(processCompletedLogs)) {
                    taskLogList.addAll(processCompletedLogs);
                }
                break;
            default:
        }

        //Perform log operations
        if (!CollectionUtils.isEmpty(taskLogList)) {
            activitiTaskLogService.createBatch(taskLogList);
        }
    }
}

2. Runtime Status Monitor:

There is one example project, activiti-api-basic-process-example and activiti-api-basic-task-example, which shows how to configure process and task monitoring at runtime.

In activiti-api-basic-process-example, process listening example:

@Bean
public ProcessRuntimeEventListener<ProcessCompletedEvent> processCompletedListener() {
    return processCompleted -> logger.info(">>> Process Completed: '"
            + processCompleted.getEntity().getName() +
            "' We can send a notification to the initiator: " + processCompleted.getEntity().getInitiator());
}

In activiti-api-basic-task-example, task listening example:

@Bean
public TaskRuntimeEventListener<TaskAssignedEvent> taskAssignedListener() {
    return taskAssigned -> logger.info(">>> Task Assigned: '"
            + taskAssigned.getEntity().getName() +
            "' We can send a notification to the assginee: " + taskAssigned.getEntity().getAssignee());
}

@Bean
public TaskRuntimeEventListener<TaskCompletedEvent> taskCompletedListener() {
    return taskCompleted -> logger.info(">>> Task Completed: '"
            + taskCompleted.getEntity().getName() +
            "' We can send a notification to the owner: " + taskCompleted.getEntity().getOwner());
}

Referring to the example, we can configure event listening in a custom process by not implementing the ActivitiEventlistener interface, inheriting the DefaultActivityBehaviorFactoryMappingConfigurer class, or implementing the ProcessEngineConfiguration Configurer interface, but simply registering the listeners for related events. Examples are as follows:

@Configuration
public class ActivitiConfiguration {

    private final RuntimeService runtimeService;

    private final ActRuTaskLogService actRuTaskLogService;

    public ActivitiConfiguration(RuntimeService runtimeService, ActRuTaskLogService actRuTaskLogService) {
        this.runtimeService = runtimeService;
        this.actRuTaskLogService = actRuTaskLogService;
    }

    @Bean
    public TaskRuntimeEventListener<TaskAssignedEvent> taskAssignedListener() {
        return taskAssigned -> {

            ExecutionEntity execution = (ExecutionEntity) runtimeService.createProcessInstanceQuery()
                    .processInstanceId(taskAssigned.getProcessInstanceId()).singleResult();
            String startUserId = execution.getStartUserId();
            String fileProcInstId = this.fileProcInstId(execution);

            // Exclude tasks that initiate applications and send messages to assignee
            if (!taskAssigned.getEntity().getAssignee().equals(startUserId)) {
                Task task = taskAssigned.getEntity();
                ActRuTaskLog taskLog = new ActRuTaskLog(task.getProcessInstanceId(), task.getId(),
                        taskAssigned.getEntity().getAssignee(), String.format(NotifyConstants.PENDING_WARN,
                        this.userName(startUserId), this.processType(fileProcInstId), this.projName(execution)),
                        NotifyTypeConstants.CANDIDATE);
                actRuTaskLogService.create(taskLog);
            }
        };
    }

    @Bean
    public TaskRuntimeEventListener<TaskCompletedEvent> taskCompletedListener() {
        return taskCompleted -> {

            ExecutionEntity execution = (ExecutionEntity) runtimeService.createProcessInstanceQuery()
                    .processInstanceId(taskCompleted.getProcessInstanceId()).singleResult();
            String startUserId = execution.getStartUserId();
            String fileProcInstId = this.fileProcInstId(execution);
            Task task = taskCompleted.getEntity();
            // Initiate approval, send message to Cc, Assist
            if (!taskCompleted.getEntity().getAssignee().equals(startUserId)) {
                // Task owner
                String owner = taskCompleted.getEntity().getOwner();
                ActRuTaskLog taskLog = new ActRuTaskLog(task.getProcessInstanceId(), task.getId(),
                        this.userName(owner), String.format(NotifyConstants.PENDING_WARN,
                        this.userName(startUserId), this.processType(fileProcInstId), this.projName(execution)),
                        NotifyTypeConstants.CANDIDATE);
                actRuTaskLogService.create(taskLog);
            } else {
                // Send a notification to the initiator of the task processing results
                ActRuTaskLog taskLog = new ActRuTaskLog(task.getProcessInstanceId(), task.getId(),
                        taskCompleted.getEntity().getAssignee(), String.format(NotifyConstants.PENDING,
                        this.userName(startUserId), this.processType(fileProcInstId), this.projName(execution),
                        this.userName(task.getAssignee()), ""), NotifyTypeConstants.PENDING);
                actRuTaskLogService.create(taskLog);
            }
        };
    }

    @Bean
    public TaskCandidateEventListener<TaskCandidateUserAddedEvent> taskCandidateUserEventListener() {
        return taskCandidateEvent -> log.info(">>> Task Candidate User Add: '"
                + taskCandidateEvent.getEntity().toString());
    }

    @Bean
    public TaskCandidateEventListener<TaskCandidateGroupAddedEvent> taskCandidateGroupEventListener() {
        return taskCandidateEvent -> log.info(">>> Task Candidate Group Add: '"
                + taskCandidateEvent.getEntity().toString());
    }

    @Bean
    public ProcessRuntimeEventListener<ProcessCompletedEvent> processCompletedEventListener() {
        return processCompletedEvent -> log.info("===>>> Process Completed: '"
                + processCompletedEvent.getEntity().toString());
    }

    /**
     * Get process form name
     *
     * @param executionEntity target of execution
     * @return form name
     */
    private String projName(ExecutionEntity executionEntity) {
        Object processInstanceName = executionEntity.getVariable("projName");
        return null == processInstanceName ? "" : processInstanceName.toString();
    }

    /**
     * Get process file ID
     *
     * @param executionEntity target of execution
     * @return File ID
     */
    private String fileProcInstId(ExecutionEntity executionEntity) {
        Object fileProcInstId = executionEntity.getVariable("fileProcInstId");
        return fileProcInstId == null ? "" : fileProcInstId.toString();
    }

    /**
     * Approval type
     *
     * @param fileProcInstId File ID
     * @return type
     */
    private String processType(String fileProcInstId) {
        return StringUtils.hasText(fileProcInstId) ? "Printing" : "contract";
    }

    /**
     * Get Name
     *
     * @param userId User ID
     * @return User Name
     */
    private String userName(String userId) {
        return userId + "Operator";
    }
}

In the first scenario, the ActivitiEvent is a superclass, and some attributes are not directly available. If you want to get it, you need to force down. Each event has a different type of subclass and needs a lot of judgment to implement, but the second method is not necessary. Because the object in the current listener is the related object of the event corresponding to the type change, the related variables and information can be obtained directly.

Keywords: Activiti listener

Added by trinitywave on Sat, 25 Dec 2021 22:32:00 +0200