Commit 1d98e90e authored by Brian Long's avatar Brian Long
Browse files

Merge branch 'develop' into stable

parents 7a506541 74084390
Loading
Loading
Loading
Loading
+69 −0
Original line number Diff line number Diff line
package com.inteligr8.activiti.mq;

import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.persistence.entity.Entity;

public interface ActivitiEntityEventListener<T extends Entity> {
	
	/**
	 * This method checks to see if this listener is for the specified entity.
	 * 
	 * @param entity An Activiti entity.
	 * @return `true` if this listener supports the entity; `false` otherwise.
	 */
	default boolean ofEntityType(Entity entity) {
		return entity == null ? false : this.ofEntityType(entity.getClass());
	}

	/**
	 * This method checks to see if this listener is for the specified entity
	 * class.
	 * 
	 * @param entityType An Activiti entity class.
	 * @return `true` if this listener supports the entity type; `false` otherwise.
	 */
	boolean ofEntityType(Class<? extends Entity> entityType);

	/**
	 * This method allows for initialization on application startup.  Any
	 * resources this listener should subscribe to, should be connected or
	 * opened.
	 */
	default void onApplicationStartup() {
	}
	
	/**
	 * This method allows for uninitialization on application shutdown.  Any
	 * resources this listener subscribes to should be disconnected or closed.
	 * 
	 * The `ActivitiEntityEventListener#onEvent()` method may still be called
	 * before startup or after shutdown.
	 */
	default void onApplicationShutdown() {
	}
	
	/**
	 * This method is fired every time there is a qualifying entity event.
	 * 
	 * @param aaevent An Activiti entity event.
	 */
	default void onEntityEvent(ActivitiEntityEvent aaevent) {
		@SuppressWarnings("unchecked")
		T entity = (T) aaevent.getEntity();
		
		this.onEntityEvent(aaevent.getType(), aaevent.getProcessDefinitionId(), aaevent.getProcessInstanceId(), aaevent.getExecutionId(), entity);
	}
	
	/**
	 * Thi smethod is fired every time there is a qualifying entity event.
	 * 
	 * @param eventType An Activiti event type; limited to `ENTITY_*`.
	 * @param processDefinitionId The unique identifier of the process definition subject to the event.
	 * @param processInstanceId The unique identifier of the process instance subject to the event.
	 * @param executionId The unique identifier of the execution subject to the event.
	 * @param entity The Activiti entity.
	 */
	void onEntityEvent(ActivitiEventType eventType, String processDefinitionId, String processInstanceId, String executionId, T entity);

}
+111 −0
Original line number Diff line number Diff line
package com.inteligr8.activiti.mq;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.persistence.entity.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.stereotype.Component;

@Component
public class ActivitiEntityEventMonitor implements ActivitiEventListener, ApplicationListener<ApplicationContextEvent> {
	
	private final Logger logger = LoggerFactory.getLogger(ActivitiEntityEventMonitor.class);
	
	@Autowired
	private List<ActivitiEntityEventListener<? extends Entity>> listeners;
	
	@Autowired
	private ProcessEngine services;
	
	/**
	 * This method is fired by Spring, the framework behind Activiti.  This
	 * forwards the application event to each listener so they can initialize
	 * or uninitialize.
	 */
	@Override
	public void onApplicationEvent(ApplicationContextEvent event) {
		if (event instanceof ContextRefreshedEvent || event instanceof ContextStoppedEvent || event instanceof ContextClosedEvent) {
			this.logger.debug("Application context refresh/stop/close detected; shutting down listeners: {}", event);
			
			for (ActivitiEntityEventListener<? extends Entity> listener : this.listeners) {
				listener.onApplicationShutdown();
			}
		}
		
		// the listener cannot be active until the context is initialized
		if (event instanceof ContextRefreshedEvent || event instanceof ContextStartedEvent) {
			this.logger.debug("Application context refresh/start detected; starting up listeners: {}", event);
			
			for (ActivitiEntityEventListener<? extends Entity> listener : this.listeners) {
				listener.onApplicationStartup();
			}
		}
	}
	
	/**
	 * This method will start monitoring for changes in deployment, app, and
	 * process definitions.
	 */
	@PostConstruct
	protected void init() {
		this.logger.debug("Bean initialized; starting to listen for any entity event ...");
		
		this.services.getRuntimeService().addEventListener(this,
				ActivitiEventType.ENTITY_CREATED,
				ActivitiEventType.ENTITY_INITIALIZED,
				ActivitiEventType.ENTITY_UPDATED,
				ActivitiEventType.ENTITY_ACTIVATED,
				ActivitiEventType.ENTITY_SUSPENDED,
				ActivitiEventType.ENTITY_DELETED);
	}
	
	/**
	 * This method will stop monitoring for changes in deployment, app, and
	 * process definitions.
	 */
	@PreDestroy
	protected void uninit() {
		this.logger.debug("Bean uninitialized; stopping listener for any entity event ...");
		
		this.services.getRuntimeService().removeEventListener(this);
	}
	
	/**
	 * This method is fired by the Activiti platform.  It is called on every
	 * entity event.
	 * 
	 * @param event An Activiti event.
	 */
	@Override
	public void onEvent(ActivitiEvent event) {
		this.logger.trace("Triggered by event: {}", event);
		
		for (ActivitiEntityEventListener<? extends Entity> listener : this.listeners) {
			ActivitiEntityEvent aaevent = (ActivitiEntityEvent) event;
			if (listener.ofEntityType((Entity) aaevent.getEntity()))
				listener.onEntityEvent(aaevent);
		}
	}
	
	@Override
	public boolean isFailOnException() {
		return true;
	}

}
+0 −473

File deleted.

Preview size limit exceeded, changes collapsed.

+85 −0
Original line number Diff line number Diff line
package com.inteligr8.activiti.mq;

import java.util.List;
import java.util.Set;

import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.persistence.entity.DeploymentEntity;
import org.activiti.engine.impl.persistence.entity.Entity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MqDeploymentEventListener implements ActivitiEntityEventListener<DeploymentEntity> {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	protected MqSubscribeLooper looper;
	
	@Autowired
	protected ProcessDefinitionRegistry registry;
	
	@Autowired
	private MqSubscriptionService subscriptionService;
	
	@Override
	public boolean ofEntityType(Class<? extends Entity> entityType) {
		return DeploymentEntity.class.isAssignableFrom(entityType);
	}
	
	@Override
	public boolean ofEntityType(Entity entity) {
		return entity instanceof DeploymentEntity;
	}
	
	@Override
	public void onEntityEvent(ActivitiEventType eventType, String processDefinitionId, String processInstanceId,
			String executionId, DeploymentEntity entity) {
		this.logger.trace("Triggered by deployment event: {}", entity);
		
		switch (eventType) {
			case ENTITY_INITIALIZED:
				// we cannot use the ProcessDefinitionEntity for ENTITY_INITIALIZED as we need the BpmnModel later and it is not yet cached
				// we must use DeploymentEntity and then dig down for the process definitions
				this.onDeploymentAddEvent((DeploymentEntity) entity);
				break;
			default:
		}
	}
	
	protected void onDeploymentAddEvent(DeploymentEntity entity) {
		this.logger.debug("Triggered by deployment addition: {}", entity);
		
		List<ProcessDefinitionEntity> procDefEntities = entity.getDeployedArtifacts(ProcessDefinitionEntity.class);
		if (procDefEntities == null)
			return;
		this.logger.debug("Found {} process definitions in deployment: {}", procDefEntities.size(), entity.getId());

		for (ProcessDefinitionEntity procDefEntity : procDefEntities) {
			this.logger.debug("Inspecting process definition: {}: {}: {}", procDefEntity.getId(), procDefEntity.getKey(), procDefEntity.getName());

			this.unsubscribeOtherMqSubscribeTasks(procDefEntity.getId());
			
			if (this.registry.isMqStart(procDefEntity.getId()))
				this.looper.loop(procDefEntity.getId());
		}
	}
	
	protected void unsubscribeOtherMqSubscribeTasks(String procDefId) {
		try {
			Set<String> executionIds = this.subscriptionService.cancelAllOtherVersions(procDefId);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Subscription executions ended early: {}: {}", procDefId, executionIds);
			} else {
				this.logger.info("Subscriptions ended early: {}: {}", procDefId, executionIds.size());
			}
		} catch (Exception e) {
			this.logger.error("The subscriptions could not be cancelled: " + procDefId, e);
		}
	}

}
+72 −17
Original line number Diff line number Diff line
@@ -130,18 +130,74 @@ public class MqExecutionService {
    	return this.cancelled(job.getExecutionId());
    }
    
    /**
     * This method cancels all the executions active on the specified process
     * definition.
     * 
     * @param executionId An execution unique identifier.
     * @return `true` if execution was cached; `false` otherwise.
     */
    public boolean cancel(String executionId) throws Exception {
    	Execution execution = this.services.getRuntimeService().createExecutionQuery().executionId(executionId).singleResult();
    	ProcessInstance pi = this.services.getRuntimeService().createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult();
    	
    	return this.cancel(pi.getProcessDefinitionId(), executionId);
    }
    
    /**
     * This method cancels all the executions active on the specified process
     * definition.
     * 
     * @param processDefinitionId A process definition unique identifier.
     * @return A set of execution identifiers that were cancelled.
     */
    public Set<String> cancelAll(String processDefinitionId) throws Exception {
    	ProcessDefinition processDefinition = this.services.getRepositoryService().getProcessDefinition(processDefinitionId);
    	String processDefinitionKey = processDefinition.getKey();
    	
    	return this.cancelAll(processDefinitionId, processDefinitionKey);
    }
    
    /**
     * @param latestProcessDefinitionId A process definition identifier to NOT clear.  All other versions will be cleared.
     * @return A set of execution identifiers that were in the now cleared map.
     */
    public synchronized Set<String> clearOtherVersions(String latestProcessDefinitionId) throws Exception {
    public synchronized Set<String> cancelAllOtherVersions(String latestProcessDefinitionId) throws Exception {
    	ProcessDefinition latestProcessDefinition = this.services.getRepositoryService().getProcessDefinition(latestProcessDefinitionId);
    	String processDefinitionKey = latestProcessDefinition.getKey();

    	Set<String> executionIds = new HashSet<>();
    	
    	Collection<String> processDefinitionIds = this.processDefinitionKeyMap.get(processDefinitionKey);
    	for (String processDefinitionId : processDefinitionIds) {
    	for (String processDefinitionId : processDefinitionIds)
    		executionIds.addAll(this.cancelAll(processDefinitionId, processDefinitionKey));
    	
    	return executionIds;
    }
    
    private synchronized boolean cancel(String processDefinitionId, String executionId) throws Exception {
		this.logger.trace("Cancelling execution: {}: {}", processDefinitionId, executionId);
		
    	Collection<String> activityIds = this.processDefinitionActivityMap.remove(processDefinitionId);
    	if (activityIds == null) {
    		this.logger.debug("No activities/executions to clear for process definition: {}", processDefinitionId);
    		return false;
    	}
    	
    	for (String activityId : activityIds) {
    		this.logger.trace("Clearing process definition activity: {}: {}", processDefinitionId, activityId);
    		Pair<String, String> key = this.toKey(processDefinitionId, activityId);
    		Collection<String> activityExecutionIds = this.activityExecutionMap.get(key);
    		if (activityExecutionIds.remove(executionId))
    			return true;
    	}
    	
    	return false;
    }
    
    private synchronized Set<String> cancelAll(String processDefinitionId, String processDefinitionKey) throws Exception {
    	Set<String> executionIds = new HashSet<>();
    	
		this.processDefinitionKeyMap.removeMapping(processDefinitionKey, processDefinitionId);
		
    	Collection<String> activityIds = this.processDefinitionActivityMap.remove(processDefinitionId);
@@ -157,7 +213,6 @@ public class MqExecutionService {
    		if (activityExecutionIds != null)
    			executionIds.addAll(activityExecutionIds);
    	}
    	}
    	
    	return executionIds;
    }
Loading