nz.co.senanque.workflow.WorkflowManagerAbstract.java Source code

Java tutorial

Introduction

Here is the source code for nz.co.senanque.workflow.WorkflowManagerAbstract.java

Source

/*******************************************************************************
 * 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<ProcessDefinition> m_allProcesses = new TreeSet<ProcessDefinition>();
    private final Set<ProcessDefinition> m_mainProcesses = new TreeSet<ProcessDefinition>();
    private final Set<QueueDefinition> m_queues = new TreeSet<QueueDefinition>();
    @SuppressWarnings("rawtypes")
    private Map<String, MessageSender> m_allMessages = new HashMap<String, MessageSender>();
    @SuppressWarnings("rawtypes")
    private Map<String, ComputeType> m_computeTypes = new HashMap<String, ComputeType>();
    private Resource m_schema;
    private Resource m_processes;

    private DefaultListableBeanFactory m_beanFactory;
    private Map<String, TimeoutProvider> m_timeouts;

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

    /* (non-Javadoc)
     * @see nz.co.senanque.workflow.WorkflowManager#getMainProcesses()
     */
    @Override
    public Set<ProcessDefinition> 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<String, MessageSender> getMessages() {
        return m_allMessages;
    }

    protected Map<String, ComputeType> 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<Audit> findHandlerTasks(ProcessInstance processInstance) {
        Stack<Audit> ret = new Stack<Audit>();
        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<QueueDefinition> 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<Audit> 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<Audit> 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;
    }

}