Activiti7 Events Monitor (Leafage.top)

I haven’t been taking notes for a while, but I’ve been doing some work on workflows recently and documenting my experience with Activiti 7.

Requirements:

  1. In the process of process initiation and operation, send notification and reminder of process approval to relevant personnel;
  2. Do not manually add it during the configuration of the flow. It cannot intrude into the process of the flow operation and affect the flow execution.

How do I get this? The official documentation for Activiti, Activiti7, is as bad as shit, feels so hard 😔…

There is an example module called Activiti-Examples. You can’t run the examples directly, you can just look at them. To run them, copy, paste, and add them to your own project. Off topic, talking about the topic…

There are several important classes or interfaces associated with Activiti:

  1. Each process information in Activiti is described by ProcessInstance, which has several states: Created, started, completed, cancelled, resumed, updated, suspended, and corresponding related event description classes are: ProcessCreatedEvent, ProcessStartedEvent, ProcessCompletedEvent, ProcessCancelledEvent, ProcessResumedEvent, ProcessUpdatedEv Ent, ProcessSuspendedEvent, etc.

  2. Each process node is described by Task in Activiti. It has several states: Created, assigned, Completed, updated, cancelled, suspended, etc. Corresponding event description classes are: TaskCreatedEvent, TaskAssignedEvent, TaskCompletedEvent, TaskUpdatedEvent, TaskCancelledEvent, TaskSuspendedEvent

How do I configure listeners?

1. Global event listener

Involves two class \ interface, global event listener ActivitiEventListener and ProcessEngineConfigurationConfigurer (has a default implementation class: DefaultActivityBehaviorFactoryMappingConfigurer)

The ActitiviEventListener interface has a void onEvent(ActivitiEvent ActivitiEvent) method that takes place when the event state 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(a);
}
Copy the code

ActivitiEvent contains the process definition ID, sample ID, execution ID, and event type information. The source code is as follows:

public interface ActivitiEvent {

  ActivitiEventType getType(a);

  String getExecutionId(a);

  String getProcessInstanceId(a);

  String getProcessDefinitionId(a);
}
Copy the code

Its event types include many, source code as follows:

public enum ActivitiEventType {

  // ENTITY: process instance that is created from the process template when initiating the process
  ENTITY_CREATED,  / / create

  ENTITY_INITIALIZED,  ENTITY_CREATED (if the creation of the entity includes the creation of child entities, this event will be emitted after the child entities are created/initialized, which is different from ENTITY_CREATED)

  ENTITY_UPDATED,  / / update

  ENTITY_DELETED,  / / delete

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

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

  / / timer
  TIMER_SCHEDULED,  / / create

  TIMER_FIRED,  / / triggers

  / / homework
  JOB_CANCELED,  / / cancel

  JOB_EXECUTION_SUCCESS,  // The command is successfully executed

  JOB_EXECUTION_FAILURE,  // Execution failed

  JOB_RETRIES_DECREMENTED,  // Fewer retries (fewer retries because the job failed)

  CUSTOM,  / / custom
  
  / / the engine
  ENGINE_CREATED,  / / create

  ENGINE_CLOSED,  / / close

  // Process node
  ACTIVITY_STARTED,  / /

  ACTIVITY_COMPLETED,  / / finish

  ACTIVITY_CANCELLED,  / / cancel

  ACTIVITY_SIGNALED,  // A signal is received

  ACTIVITY_COMPENSATE,  // will be compensated
  
  ACTIVITY_MESSAGE_SENT,  // Message sent
 
  ACTIVITY_MESSAGE_WAITING,  // Message waiting

  ACTIVITY_MESSAGE_RECEIVED,  // Message receive

  ACTIVITY_ERROR_RECEIVED,  // Failed to receive
  
  // Process history
  HISTORIC_ACTIVITY_INSTANCE_CREATED,  / / create
  
  HISTORIC_ACTIVITY_INSTANCE_ENDED,  / / end

  // Queue process
  SEQUENCEFLOW_TAKEN,  / / has taken

  UNCAUGHT_BPMN_ERROR,  // No BPMN exception is found

  / / variable
  VARIABLE_CREATED,  / / create

  VARIABLE_UPDATED,  / / update

  VARIABLE_DELETED,  / / delete

  / / task
  TASK_CREATED,  // Create (it comes 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,  // Done (this is triggered before the ENTITY_DELETE event. When the task is part of the process, the event will be ACTIVITY_COMPLETE, corresponding to the node that completed the task, before the process continues.)

  / / process
  PROCESS_STARTED,  / /

  PROCESS_COMPLETED,  // Complete (triggered after the ACTIVITY_COMPLETED event on the last node. The process ends when it reaches a state where there are no further 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

  / / members
  MEMBERSHIP_CREATED,  // The user is added to a group

  MEMBERSHIP_DELETED,  // The user was deleted from a group

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

ProcessEngineConfigurationConfigurer void in the configure (SpringProcessEngineConfiguration SpringProcessEngineConfiguration) method can add a custom event listener, the listener process scope for 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(newMappingAwareActivityBehaviorFactory(variablesMappingProvider, processVariablesInitiator)); }}Copy the code

How do I listen for events?

  1. ActivitiEventListener interface, rewrite void onEvent(ActivitiEvent) method;
  2. Added configuration class, Inheritance DefaultActivityBehaviorFactoryMappingConfigurer class (or implement ProcessEngineConfigurationConfigurer interface) and re-written configura () method, Add custom listener to SpringProcessEngineConfiguration attribute implementation class;

The following is an example:

@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(newActivitiTaskEventListener(activitiTaskLogService, userService))); }}Copy the code
public class ActivitiTaskEventListener implements ActivitiEventListener {

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

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

        List<ActivitiTaskLog> taskLogList = new ArrayList<>();
        switch (activitiEvent.getType()) {
            // After the task is executed
            case TASK_COMPLETED:
                // The task is processed and given to the creator, the candidate for the next task
                List<ActivitiTaskLog> taskCompletedLogs = this.taskCompleted(processInstance, activitiEvent.getExecutionId());
                if(! CollectionUtils.isEmpty(taskCompletedLogs)) { taskLogList.addAll(taskCompletedLogs); }break;
            case PROCESS_COMPLETED:
                // When the process is complete, send a notification to the founder, facilitator, and cc
                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); }}}Copy the code

2. Runtime status listener:

In the example, there are two example projects, activiti-api-basic-process-example and activiti-api-basic-task-example, which show how to configure process and task listening at runtime.

Activiti-api-basic-process-example is an example of a process listener:

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

Activiti-api-basic-task-example:

@Bean
public TaskRuntimeEventListener<TaskAssignedEvent> taskAssignedListener(a) {
    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(a) {
    return taskCompleted -> logger.info(">>> Task Completed: '"
            + taskCompleted.getEntity().getName() +
            "' We can send a notification to the owner: " + taskCompleted.getEntity().getOwner());
}
Copy the code

Referring to the example, we can customize the event listener configuration in the process. In this way, we do not need to implement the ActivitiEventlistener interface. Also do not need to inherit DefaultActivityBehaviorFactoryMappingConfigurer class or implement ProcessEngineConfigurationConfigurer interface, you just need to register related event listeners. The following is an example:

@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(a) {
        return taskAssigned -> {

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

            // Exclude the task of initiating the application, sending a message 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(a) {
        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 a message to the cc person and the helper
            if(! taskCompleted.getEntity().getAssignee().equals(startUserId)) {// Mission 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 about the result of the task processing
                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(a) {
        return taskCandidateEvent -> log.info(">>> Task Candidate User Add: '"
                + taskCandidateEvent.getEntity().toString());
    }

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

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

    /** * get the process form name **@paramExecutionEntity Executes the object *@returnThe form of * /
    private String projName(ExecutionEntity executionEntity) {
        Object processInstanceName = executionEntity.getVariable("projName");
        return null == processInstanceName ? "" : processInstanceName.toString();
    }

    /** * Get process file ID **@paramExecutionEntity Executes the object *@returnThe file ID * /
    private String fileProcInstId(ExecutionEntity executionEntity) {
        Object fileProcInstId = executionEntity.getVariable("fileProcInstId");
        return fileProcInstId == null ? "" : fileProcInstId.toString();
    }

    /** * Approval type **@paramFileProcInstId File ID *@returnType * /
    private String processType(String fileProcInstId) {
        return StringUtils.hasText(fileProcInstId) ? "Being there" : "Contract";
    }

    /** * get the name **@paramUserId userId *@returnUser name */
    private String userName(String userId) {
        return userId + "Operator"; }}Copy the code

In the first scheme, ActivitiEvent is a superclass, and some attributes cannot be obtained directly. If you want to obtain them, you need to make strong downward transformation. In addition, the subclasses of each type of event are different, which requires a lot of judgment. Because the object in the current listener is the relevant object of the event corresponding to the change type, it can directly obtain the relevant variables and information.