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:
- Send notification reminders for process approval to related personnel during process initiation and process operation.
- 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:
-
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.
-
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?
- Implement the ActivitiEventListener interface and override the void onEvent method;
- 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.