All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nz.co.senanque.workflow.WorkflowManagerAbstract Maven / Gradle / Ivy

There is a newer version: 2.4.0
Show newest version
/*******************************************************************************
 * Copyright (c)2014 Prometheus Consulting
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package nz.co.senanque.workflow;

import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;

import nz.co.senanque.logging.HashIdLogger;
import nz.co.senanque.messaging.MessageMapper;
import nz.co.senanque.messaging.MessageSender;
import nz.co.senanque.process.instances.ComputeType;
import nz.co.senanque.process.instances.ProcessDefinition;
import nz.co.senanque.process.instances.QueueDefinition;
import nz.co.senanque.process.instances.TaskBase;
import nz.co.senanque.process.instances.TaskEnd;
import nz.co.senanque.process.instances.TaskIf;
import nz.co.senanque.process.instances.TaskTry;
import nz.co.senanque.process.instances.TimeoutProvider;
import nz.co.senanque.schemaparser.FieldDescriptor;
import nz.co.senanque.workflow.instances.Audit;
import nz.co.senanque.workflow.instances.DeferredEvent;
import nz.co.senanque.workflow.instances.ProcessInstance;
import nz.co.senanque.workflow.instances.TaskStatus;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.Resource;
import org.springframework.messaging.Message;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

/**
 * This implements the non-database functionality of the workflow manager.
 * Injected beans handle the database activity
 * @author Roger Parkinson
 *
 */
public abstract class WorkflowManagerAbstract implements WorkflowManager, BeanFactoryAware {

	private static final Logger log = LoggerFactory
			.getLogger(WorkflowManagerAbstract.class);
    private final Set m_allProcesses = new TreeSet();
    private final Set m_mainProcesses = new TreeSet();
	private final Set m_queues = new TreeSet();
    @SuppressWarnings("rawtypes")
	private Map m_allMessages = new HashMap();
    @SuppressWarnings("rawtypes")
	private Map m_computeTypes = new HashMap();
	private Resource m_schema;
	private Resource m_processes;
	
	private DefaultListableBeanFactory m_beanFactory;
	private Map m_timeouts;

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getAllProcesses()
	 */
	@Override
	public Set getAllProcesses() {
		return m_allProcesses;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getMainProcesses()
	 */
	@Override
	public Set getMainProcesses() {
		return m_mainProcesses;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getMessage(java.lang.String)
	 */
	@Override
	public MessageSender getMessage(String messageName) {
		return m_allMessages.get(messageName);
	}
	
	protected Map getMessages() {
		return m_allMessages;
	}

	protected Map getComputeTypes() {
		return m_computeTypes;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getProcessDefinition(java.lang.String)
	 */
	@Override
	public ProcessDefinition getProcessDefinition(String name) {
		for (ProcessDefinition pd: m_allProcesses) {
			if (pd.getName().equals(name)) {
				return pd;
			}
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getTask(nz.co.senanque.process.instances.ProcessDefinition, long)
	 */
	@Override
	public TaskBase getTask(ProcessDefinition pd, long taskId) {
		for (TaskBase task: pd.getTasks()) {
			if (task.getTaskId() == taskId) {
				return task;
			}
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getCurrentTask(nz.co.senanque.workflow.instances.ProcessInstance)
	 */
	@Override
	public TaskBase getCurrentTask(ProcessInstance processInstance) {
		if (processInstance == null) {
			throw new NullPointerException("processInstance was null");
		}
		return getTask(processInstance.getProcessDefinitionName(),processInstance.getTaskId());
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getTask(java.lang.String, java.lang.Long)
	 */
	@Override
	public TaskBase getTask(String processDefinitionName, Long taskid) {
		ProcessDefinition pd = getProcessDefinition(processDefinitionName);
		if (pd == null) {
			throw new WorkflowException("Could not find active process for "+processDefinitionName);
		}
		TaskBase task = getTask(pd,taskid);
		if (task == null) {
			throw new WorkflowException("Could not find task for "+processDefinitionName+":"+taskid);
		}
		return task;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getField(nz.co.senanque.workflow.instances.ProcessInstance, nz.co.senanque.schemaparser.FieldDescriptor)
	 */
	@Override
	public abstract Object getField(ProcessInstance processInstance, FieldDescriptor fd);

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#findHandlerTasks(nz.co.senanque.workflow.instances.ProcessInstance)
	 */
	@Transactional
	public List findHandlerTasks(ProcessInstance processInstance) {
		Stack ret = new Stack();
		for (Audit audit : processInstance.getAudits()) {
			if (audit.isHandler() && audit.getStatus() != null) {
				ret.push(audit);
			}
		}
		return ret;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getTask(nz.co.senanque.workflow.instances.DeferredEvent)
	 */
	@Override
	public TaskBase getTask(DeferredEvent deferredEvent) {
		return getTask(deferredEvent.getProcessDefinitionName(),deferredEvent.getTaskId());
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#findInterruptedTask(nz.co.senanque.workflow.instances.ProcessInstance)
	 */
	@Transactional
	public TaskBase findInterruptedTask(ProcessInstance processInstance) {
		TaskBase ret = null;
		for (Audit audit : processInstance.getAudits()) {
			if (audit.isInterrupted()) {
				ret = getTask(
						audit.getProcessDefinitionName(), audit.getTaskId());
				audit.setInterrupted(false);
				break;
			}
		}
		return ret;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#addMainProcess(nz.co.senanque.process.instances.ProcessDefinition)
	 */
	@Override
	public void addMainProcess(ProcessDefinition processDefinition) {
		m_mainProcesses.add(processDefinition);
		addSubProcess(processDefinition);
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#addSubProcess(nz.co.senanque.process.instances.ProcessDefinition)
	 */
	@Override
	public void addSubProcess(ProcessDefinition processDefinition) {
		m_allProcesses.add(processDefinition);
	}
	
	public String trimComment(String comment) {
		String ret = comment.trim();
		if (ret.length() > 510) {
			return ret.substring(0, 510);
		}
		return ret;
	}

	protected Audit createAudit(ProcessInstance processInstance, TaskBase task) {
		Audit audit = new Audit();
		audit.setCreated(new Timestamp(System.currentTimeMillis()));
		audit.setTaskId(task.getTaskId());
		audit.setProcessDefinitionName(task.getOwnerProcess().getName());
		audit.setComment(trimComment(task.toString()));
		audit.setHandler(task.getHandler());
		audit.setInterrupted(false);
		audit.setLockedBy(processInstance.getLockedBy());
		audit.setStatus(processInstance.getStatus());
		audit.setParentId(processInstance.getId());
		processInstance.getAudits().add(audit);
		return audit;
	}
	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#execute(nz.co.senanque.workflow.instances.ProcessInstance)
	 */
	@Transactional
	public void execute(ProcessInstance processInstance) {
		HashIdLogger.log(this,"execute");
		TaskBase task = getCurrentTask(processInstance);
		if (processInstance.getStatus() == TaskStatus.DONE) {
			return;
		}
		while (true) {
			log.debug("processInstanceId={} processName={} taskId={} status={}",processInstance.getId(),task.getOwnerProcess().getName(),task.getTaskId(), processInstance.getStatus());
			boolean b = true;
			Audit audit = createAudit(processInstance, task);
			try {
				if (processInstance.getStatus()==TaskStatus.ABORTING)
				{
					// Someone (external) set our status to ABORTING.
					// Don't attempt to execute the current instruction just throw an abort error.
					throw new AbortException(processInstance.getComment());
				}
				if (processInstance.getStatus()==TaskStatus.TIMEOUT)
				{
					// Just throw an exception and the exception handler looks after it.
					throw new TimeoutException();
				}
				if (processInstance.getStatus()==TaskStatus.GO)
				{
					if (task instanceof TaskEnd) {
						throw new WorkflowException("Trying to restart an end task, should never happen "+task.toString());
					}
					log.debug("moving on from "+task.toString());
					// simple search for the task after this one.
					task = task.getNextTask(processInstance);
					if (task == null) {
						// There is no task, this should never happen
						throw new WorkflowException("Failed to find a next task");
					}
					// Not the end of the process, so ensure the next task is pending.
					audit.setTaskId(task.getTaskId());
					audit.setProcessDefinitionName(task.getOwnerProcess().getName());
					audit.setComment(trimComment(task.toString()));
					audit.setHandler(task.getHandler());
					task.loadTask(processInstance);
				}
				audit.setStatus(TaskStatus.PENDING);
				ProcessInstanceUtils.clearQueue(processInstance, TaskStatus.PENDING);
				b = task.execute(processInstance);
			} catch (ContinueException e) {
				task = getCurrentTask(processInstance);
				continue;
			} catch (RetryException e) {
				task = getCurrentTask(processInstance);
				continue;
			} catch (ErrorException e) {
				audit.setStatus(TaskStatus.ERROR);
				if (!handleError(processInstance)) {
					// no appropriate handler, fall back to abort handler
					audit.setStatus(TaskStatus.ERROR);
					audit.setInterrupted(true);
					if (!handleAbort(processInstance)) {
						// Still no handler so we are screwed
						// Setting this status in the process 
						// instance will stop it
						processInstance.setStatus(TaskStatus.ABORTED);
					}
				} else {
					task = getCurrentTask(processInstance);
					continue;
				}
			} catch (AbortException e) {
				audit.setInterrupted(true);
				audit.setStatus(TaskStatus.ABORTED);
				audit.setComment(e.getMessage());
				if (!handleAbort(processInstance)) {
					// No handler found so just force an abort
					// Setting this status in the process instance will stop it
					processInstance.setStatus(TaskStatus.ABORTED);
					tickleParentProcess(processInstance,TaskStatus.ABORTED); // this should abort the sibling processes as well as the parent.
					break;
				} else {
					task = getCurrentTask(processInstance);
					continue;
				}
			} catch (TimeoutException e) {
				processInstance.getAudits().remove(audit);
				if (!handleTimeout(processInstance, audit)) {
					// No handler found so just force an abort
					// Setting this status in the process instance will stop it
					processInstance.setStatus(TaskStatus.ABORTED);
					tickleParentProcess(processInstance,processInstance.getStatus());
					break;
				} else {
					task = getCurrentTask(processInstance);
					continue;
				}
			}
			if (!b) {
				// task was not completed, exit the loop
				processInstance.setStatus(TaskStatus.WAIT);
				audit.setStatus(TaskStatus.WAIT);
				break;
			} else {
				if (task instanceof TaskEnd) {
					endOfProcessDetected(processInstance,audit);
					break;
				} else if (task instanceof TaskTry) {
					task = ((TaskTry)task).getFirstTask();
				} else if (task instanceof TaskIf) {
					task = ((TaskIf)task).getConditionalTask(processInstance);
				} else {
					task = getCurrentTask(processInstance).getNextTask(processInstance);
				}
				if (task == null) {
					throw new WorkflowException("Trying to step to a task when there is none");
				}
				// Not the end of the process, so ensure the next task is pending.
				task.loadTask(processInstance);
			}
		}
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#execute(long)
	 */
	@Override
	public abstract void execute(long id);

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getQueues()
	 */
	@Override
	public Set getQueues() {
		return m_queues;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getQueue(java.lang.String)
	 */
	@Override
	public QueueDefinition getQueue(String queueName) {
		if (!StringUtils.hasText(queueName)) {
			return null;
		}
		for (QueueDefinition qd: m_queues) {
			if (qd.getName().equals(queueName)) {
				return qd;
			}
		}
		log.warn("failed to find queue: {}", queueName);
		return null;
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#execute(nz.co.senanque.workflow.instances.DeferredEvent)
	 */
	@Transactional
	public abstract void executeDeferredEvent(long deferredEventId);

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getComputeType(java.lang.String)
	 */
	@Override
	public ComputeType getComputeType(String computeName) {
		return m_computeTypes.get(computeName);
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getTimeoutProvider(java.lang.String)
	 */
	@Override
	public TimeoutProvider getTimeoutProvider(String timeoutProviderName) {
		return m_timeouts.get(timeoutProviderName);
	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getValidationEngine()
	 */
//	@Override
//	public ValidationEngine getValidationEngine() {
//		return null;
//	}

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#processMessage(nz.co.senanque.workflow.instances.ProcessInstance, org.springframework.integration.Message, nz.co.senanque.messaging.MessageMapper)
	 */
	@Override
	public abstract void processMessage(ProcessInstance processInstance,
			Message message, MessageMapper messageMapper);

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#getContext(java.lang.String)
	 */
	@Override
	public abstract Object getContext(String objectInstance);
	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#mergeContext(java.lang.Object)
	 */
	@Override
	public abstract void mergeContext(Object context);

	/* (non-Javadoc)
	 * @see nz.co.senanque.workflow.WorkflowManager#createContextDescriptor(java.lang.Object)
	 */
	@Override
	public abstract String createContextDescriptor(Object o);

	protected void findBeans() {
		m_computeTypes = m_beanFactory.getBeansOfType(ComputeType.class);
		m_allMessages = m_beanFactory.getBeansOfType(MessageSender.class);
		m_timeouts = m_beanFactory.getBeansOfType(TimeoutProvider.class);
	}

	protected boolean handleError(ProcessInstance processInstance) {
		List audits = findHandlerTasks(processInstance);
		for (Audit audit : audits) {
			TaskBase taskBase = getTask(audit);
			if (taskBase instanceof TaskTry) {
				TaskTry taskTry = (TaskTry) taskBase;
				tidyAuditTrail(processInstance,taskTry.getTaskId());
				ProcessDefinition pd = taskTry.getErrorHandler();
				if (pd == null) {
					continue;
				}
				pd.startProcess(processInstance);
				return true;
			}
		}
		return false;
	}

	protected boolean handleTimeout(ProcessInstance processInstance, Audit audit) {
		dumpAuditTrail(processInstance);
		TaskBase tb = getCurrentTask(processInstance);
		if (tb instanceof TaskTry) {
			TaskTry taskTry = (TaskTry) tb;
			ProcessDefinition pd = taskTry.getTimeoutHandler();
			pd.startProcess(processInstance);
			return true;
		}
		return false;
	}
	private Audit tidyAuditTrail(ProcessInstance processInstance, long taskId) {
		boolean found = false;
		Audit ret = null;
		for (Audit audit: processInstance.getAudits()) {
			if (audit.getProcessDefinitionName() != null && processInstance.getProcessDefinitionName().equals(audit.getProcessDefinitionName()) && processInstance.getTaskId() == taskId ) {
				found = true;
				ret = audit;
			} else if (found) {
				audit.setHandler(false);
			}
		}
		return ret;
	}
	private void dumpAuditTrail(ProcessInstance processInstance) {
		if (!log.isDebugEnabled()) {
			return;
		}
		for (Audit audit: processInstance.getAudits()) {
			log.debug("{} {} {} {} {} {} {}",
					audit.getId(),audit.getProcessDefinitionName(),
					audit.getTaskId(),audit.getStatus(),audit.getComment(),audit.isHandler(),audit.isInterrupted());
		}
	}

	protected abstract void tickleParentProcess(ProcessInstance processInstance, TaskStatus status);
	protected abstract TaskBase endOfProcessDetected(ProcessInstance processInstance, Audit currentAudit);

	protected TaskBase getTask(Audit audit) {
		return getTask(audit.getProcessDefinitionName(), audit.getTaskId());
		
	}
	protected boolean handleAbort(ProcessInstance processInstance) {
		List audits = findHandlerTasks(processInstance);
		for (Audit audit : audits) {
			TaskBase taskBase = getTask(audit);
			if (taskBase instanceof TaskTry) {
				TaskTry taskTry = (TaskTry) taskBase;
				tidyAuditTrail(processInstance,taskTry.getTaskId());
				ProcessDefinition pd = taskTry.getAbortHandler();
				if (pd == null) {
					continue;
				}
				pd.startProcess(processInstance);
				return true;
			}
		}
		return false;
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		m_beanFactory = (DefaultListableBeanFactory)beanFactory;
	}

	public Resource getSchema() {
		return m_schema;
	}

	public void setSchema(Resource schema) {
		m_schema = schema;
	}

	public Resource getProcesses() {
		return m_processes;
	}

	public void setProcesses(Resource processes) {
		m_processes = processes;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy