com.workplacesystems.queuj.process.ProcessWrapper.java Source code

Java tutorial

Introduction

Here is the source code for com.workplacesystems.queuj.process.ProcessWrapper.java

Source

/*
 * Copyright 2010 Workplace Systems PLC (http://www.workplacesystems.com/).
 *
 * 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 com.workplacesystems.queuj.process;

import com.workplacesystems.queuj.Access;
import com.workplacesystems.queuj.Occurrence;
import com.workplacesystems.queuj.Output;
import com.workplacesystems.queuj.Process;
import com.workplacesystems.queuj.ProcessListener;
import com.workplacesystems.queuj.Queue;
import com.workplacesystems.queuj.QueueOwner;
import com.workplacesystems.queuj.Resilience;
import com.workplacesystems.queuj.Visibility;
import com.workplacesystems.queuj.process.ProcessEntity.Status;
import com.workplacesystems.queuj.process.jpa.ProcessImpl;
import com.workplacesystems.utilsj.Callback;
import com.workplacesystems.queuj.utils.QueujException;
import com.workplacesystems.queuj.utils.User;
import com.workplacesystems.utilsj.collections.FilterableArrayList;
import com.workplacesystems.utilsj.collections.FilterableCollection;
import com.workplacesystems.utilsj.collections.IterativeCallback;
import java.io.Serializable;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author dave
 */
public class ProcessWrapper<K extends Serializable & Comparable> implements Comparable<ProcessWrapper<K>> {

    private final static Log log = LogFactory.getLog(ProcessWrapper.class);

    private String queueOwner;

    private ProcessEntity<K> process;

    private boolean isPersistent;

    private volatile boolean deleted = false;

    private volatile boolean rescheduleRequired;

    private ProcessWrapper(String queueOwner, ProcessEntity<K> process, boolean isPersistent) {
        this.queueOwner = queueOwner;
        this.process = process;
        this.isPersistent = isPersistent;
    }

    public static <K extends Serializable & Comparable> ProcessWrapper<K> getNewInstance(String queueOwner,
            ProcessEntity<K> process, boolean isPersistent) {
        ProcessWrapper<K> wrapper = new ProcessWrapper<K>(queueOwner, process, isPersistent);
        return wrapper;
    }

    public static <K extends Serializable & Comparable> ProcessWrapper<K> getNewInstance(String queueOwner,
            boolean isPersistent, Map<String, Object> server_options) {
        ProcessWrapper<K> processWrapper;
        if (isPersistent) {
            ProcessPersistence<ProcessEntity<K>, K> processHome = (ProcessPersistence<ProcessEntity<K>, K>) QueujFactory
                    .getPersistence(queueOwner, server_options);
            processHome.clearInstance();
            ProcessEntity<K> process = processHome.getInstance();
            processWrapper = getNewInstance(queueOwner, process, true);
        } else {
            ProcessEntity<K> process = (ProcessEntity<K>) QueujFactory.getNewProcessEntity(queueOwner, false,
                    server_options);
            process.setProcessId(process.getNextProcessId());
            processWrapper = getNewInstance(queueOwner, process, false);
        }
        return processWrapper;
    }

    private ProcessPersistence<ProcessEntity<K>, K> getProcessPersistence() {
        if (!isPersistent)
            return null;
        ProcessPersistence<ProcessEntity<K>, K> processHome = (ProcessPersistence<ProcessEntity<K>, K>) QueujFactory
                .getPersistence(queueOwner, getServerOptions());
        processHome.clearInstance();
        processHome.setId(process.getProcessId());
        this.process = processHome.getInstance();
        return processHome;
    }

    public ProcessServer getContainingServer() {
        return QueujFactory.getProcessServer(queueOwner, getServerOptions());
    }

    public boolean isAddedToServer() {
        return getContainingServer().contains(this);
    }

    /** Should this process be added to the indexes */
    boolean addToIndex() {
        return isAddedToServer() || getContainingServer().areIndexesLoading();
    }

    public boolean isPersistent() {
        return isPersistent;
    }

    public ProcessPersistence<ProcessEntity<K>, K> setDetails(String process_name, Queue queue,
            String process_description, User user, Occurrence occurrence, Visibility visibility, Access access,
            Resilience resilience, Output output, FilterableArrayList<? extends SequencedProcess> pre_processes,
            FilterableArrayList<? extends SequencedProcess> post_processes, boolean keep_completed,
            FilterableArrayList<? extends ProcessListener> process_listeners, Locale locale,
            Map<String, Object> implementation_options) {

        ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();

        process.setProcessWrapper(this);

        ProcessParameters parameters = new ProcessParameters();
        parameters.setValue(ProcessParameters.POST_PROCESSES, post_processes);
        parameters.setValue(ProcessParameters.PRE_PROCESSES, pre_processes);
        parameters.setValue(ProcessParameters.PROCESS_LISTENERS, process_listeners);
        process.setParameters(parameters);

        process.setImplementationOptions(implementation_options);

        process.setProcessName(process_name);
        if (queueOwner != null)
            process.setQueueOwnerId(queueOwner);
        process.setUUID(UUID.randomUUID().toString());
        process.setQueue(queue);
        process.setDescription(process_description);
        if (user != null)
            process.setUserId(user.getUserId());
        updateOccurrence(occurrence, false);
        rescheduleRequired = false;
        process.setVisibility(visibility);
        process.setAccess(access);
        process.setResilience(resilience);
        process.setOutput(output);
        process.setAssociatedReport(output.producesOutput());
        process.setKeepCompleted(keep_completed);
        process.setLocale(locale);

        process.setCreationTimestamp(new Timestamp((new Date()).getTime()));

        return processHome;
    }

    private final static String REPORT_TYPE = "report_type";

    public void setReportType(String report_type) {
        setParameter(REPORT_TYPE, report_type);
    }

    public String getReportType() {
        return (String) getParameter(REPORT_TYPE);
    }

    private final static String SOURCE_NAME = "page_name"; // page_name for legacy

    public void setSourceName(String source_name) {
        setParameter(SOURCE_NAME, source_name);
    }

    public String getSourceName() {
        return (String) getParameter(SOURCE_NAME);
    }

    public Serializable setParameter(String parameterName, Serializable parameterValue) {
        ProcessParameters parameters = getParameters();
        Serializable ret = parameters.setValue(parameterName, parameterValue);
        process.setParameters(parameters);
        return ret;
    }

    public Map<String, Object> getServerOptions() {
        return process.getServerOptions();
    }

    //-------------------------------------------- Get job details --------------------------------------------

    public Queue getQueue() {
        return process.getQueue();
    }

    public String getUserId() {
        return process.getUserId();
    }

    public Locale getLocale() {
        return process.getLocale();
    }

    public ProcessEntity getProcessEntity() {
        return process;
    }

    public GregorianCalendar getNextRunTime() {
        Date initialStartDate = process.getScheduledTimestamp() == null ? process.getCreationTimestamp()
                : process.getScheduledTimestamp();
        GregorianCalendar initialStart = new GregorianCalendar();
        initialStart.setTime(initialStartDate);
        return process.getResilience().getNextRunTime(process.getOccurrence(), isFailed(), process.getAttempt(),
                initialStart, process.getRunCount());
    }

    public boolean isNotRun() {
        return process.getStatus().equals(Status.NOT_RUN);
    }

    public boolean isRunning() {
        return process.getStatus().equals(Status.RUNNING);
    }

    public boolean isWaitingToRun() {
        return process.getStatus().equals(Status.LOCKED);
    }

    public boolean isComplete() {
        return process.getStatus().equals(Status.RUN_OK);
    }

    public boolean isRunError() {
        return process.getStatus().equals(Status.RUN_ERROR);
    }

    public boolean isRestarted() {
        return process.getStatus().equals(Status.RESTARTED);
    }

    public boolean isFailed() {
        return process.getResultCode() > 0 || isRunError()
                || (((isRunning() || isWaitingToRun()) && processRunner == null));
    }

    public boolean isVisible(User user, QueueOwner active_partition) {
        return process.getVisibility().isVisible(new Process<K>(this), user, active_partition);
    }

    public boolean isDueImminently(GregorianCalendar dueTime) {
        if (isComplete() || isDeleted())
            return false;

        GregorianCalendar nextRun = getNextRunTime();
        if (nextRun == null) // No run is due
            return false;

        if (nextRun.after(dueTime))
            return false;

        return true;
    }

    public boolean rescheduleRequired(boolean otherStatus) {
        synchronized (mutex) {
            return ((isSleeping() && rescheduleRequired) || (isNotRun() && processRunner != null
                    && processRunner.isSleeping() && (isDeleted() || otherStatus)));
        }
    }

    public boolean isDeleted() {
        return deleted;
    }

    Date getCreationTimestamp() {
        return process.getCreationTimestamp();
    }

    public String getProcessName() {
        return process.getProcessName();
    }

    public String getDescription() {
        return process.getDescription();
    }

    public String getStartedTimestamp() {
        return (new SimpleDateFormat("HH:mm:ss EEE, d MMM yyyy")).format(process.getStartedTimestamp());
    }

    public String getStatusDescription() {
        return process.getStatus().getDescription();
    }

    public String getStatusImg() {
        if (isFailed())
            return "job_failed";

        if (isRunning())
            return "job_running";

        return null;
    }

    public String getQueueOwner() {
        return queueOwner;
    }

    public K getProcessKey() {
        return process.getProcessId();
    }

    public Integer getProcessVersion() {
        return process.getVersion();
    }

    public int getAttempt() {
        return process.getAttempt();
    }

    boolean keepCompleted() {
        return process.isKeepCompleted();
    }

    int getResultCode() {
        return process.getResultCode();
    }

    public ProcessParameters getParameters() {
        return process.getParameters();
    }

    public Serializable getParameter(String parameterName) {
        return getParameters().getValue(parameterName);
    }

    @Override
    public int compareTo(ProcessWrapper<K> otherProcess) {

        if (getCreationTimestamp().before(otherProcess.getCreationTimestamp()))
            return -1;

        if (getCreationTimestamp().after(otherProcess.getCreationTimestamp()))
            return 1;

        int cmp = getProcessName().compareTo(otherProcess.getProcessName());
        if (cmp != 0)
            return cmp;

        K id1 = getProcessKey();
        K id2 = otherProcess.getProcessKey();
        cmp = id1.getClass().toString().compareTo(id2.getClass().toString());
        if (cmp != 0)
            return cmp;

        return id1.compareTo(id2);
    }

    boolean canRun(GregorianCalendar currentTime) {
        String debug_id = "";
        if (log.isDebugEnabled()) {
            // Lets get the process details as a readable String
            debug_id = "Process '" + getProcessKey() + ", " + getProcessName() + "'";
            // Log the start of canRun
            log.debug("Checking whether " + debug_id + " can run.");
        }

        // Now ask the Queue if we're ok to run
        if (!getQueue().canRun(new Process<K>(this))) {
            if (log.isDebugEnabled())
                log.debug("Queue for " + debug_id + " reports that it cannot run.");
            return false;
        }
        return true;
    }

    public boolean canDelete(User user, QueueOwner activeQueueOwner) {
        if (isRunning())
            return false;

        return process.getAccess().canDelete(new Process<K>(this), user, activeQueueOwner);
    }

    public boolean canRestart(User user, QueueOwner activeQueueOwner) {
        if (!isFailed())
            return false;

        return process.getAccess().canRestart(new Process<K>(this), user, activeQueueOwner);
    }

    //-------------------------------------------- Update job details/status --------------------------------------------

    public void updateOccurrence(final Occurrence occurrence) {
        updateOccurrence(occurrence, true);
    }

    private void updateOccurrence(final Occurrence occurrence, final boolean doStart) {
        doTransaction(new Callback<ProcessWrapper<K>>() {

            @Override
            protected void doAction() {
                ProcessPersistence<ProcessEntity<K>, K> processHome = null;
                if (doStart)
                    processHome = getProcessPersistence();
                process.setOccurrence(occurrence);
                process.setRunCount(0);
                process.setAttempt(0);
                process.setScheduledTimestamp((new GregorianCalendar()).getTime());
                process.setStatus(Status.NOT_RUN);
                process.setResultCode(0);
                if (doStart && isPersistent)
                    processHome.update();

                _return(ProcessWrapper.this);
            }
        }, new Callback<Void>() {

            @Override
            protected void doAction() {
                rescheduleRequired = true;
            }
        }, doStart);
    }

    public void updateRunError() {
        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                getContainingServer().removeProcessFromIndex(ProcessWrapper.this);
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.updateRunError called for " + process.getProcessId() + "("
                        + process.getVersion() + ")");
                process.setStatus(Status.RUN_ERROR);
                process.setAttempt(process.getAttempt() + 1);
                if (isPersistent)
                    processHome.update();
                getContainingServer().addProcessToIndex(ProcessWrapper.this);
                _return(ProcessWrapper.this);
            }
        });
    }

    boolean updateRestart() {
        if (isFailed()) {
            doTransaction(new Callback() {

                @Override
                protected void doAction() {
                    getContainingServer().removeProcessFromIndex(ProcessWrapper.this);

                    ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                    log.debug("ProcessWrapper.updateRestart called for " + process.getProcessId() + "("
                            + process.getVersion() + ")");
                    process.setStatus(Status.RESTARTED);
                    process.setAttempt(0);
                    process.setResultCode(0);
                    if (isPersistent)
                        processHome.update();
                    _return(ProcessWrapper.this);
                }
            });
            return true;
        }
        return isRestarted();
    }

    void updateRunning(final GregorianCalendar runTime) {
        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                getContainingServer().removeProcessFromIndex(ProcessWrapper.this);
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.updateRunning called for " + process.getProcessId() + "("
                        + process.getVersion() + ")");
                process.setStatus(Status.RUNNING);
                process.setResultCode(0);

                process.setStartedTimestamp(new Date());
                process.setScheduledTimestamp(runTime.getTime());
                if (isPersistent)
                    processHome.update();

                getContainingServer().addProcessToIndex(ProcessWrapper.this);
                _return(ProcessWrapper.this);
            }
        });
    }

    public void updateRunning() {
        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                getContainingServer().removeProcessFromIndex(ProcessWrapper.this);
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.updateRunning called for " + process.getProcessId() + "("
                        + process.getVersion() + ")");
                process.setStatus(Status.RUNNING);
                process.setResultCode(0);

                if (isPersistent)
                    processHome.update();

                getContainingServer().addProcessToIndex(ProcessWrapper.this);
                _return(ProcessWrapper.this);
            }
        });
    }

    void updateNotRun() {
        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                getContainingServer().removeProcessFromIndex(ProcessWrapper.this);
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.updateNotRun called for " + process.getProcessId() + "("
                        + process.getVersion() + ")");
                process.setStatus(Status.NOT_RUN);
                process.setResultCode(0);
                if (isPersistent)
                    processHome.update();
                getContainingServer().addProcessToIndex(ProcessWrapper.this);
                _return(ProcessWrapper.this);
            }
        });
    }

    void updateLocked() {
        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                getContainingServer().removeProcessFromIndex(ProcessWrapper.this);
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.updateLocked called for " + process.getProcessId() + "("
                        + process.getVersion() + ")");
                process.setStatus(Status.LOCKED);
                process.setResultCode(0);
                if (isPersistent)
                    processHome.update();
                getContainingServer().addProcessToIndex(ProcessWrapper.this);
                _return(ProcessWrapper.this);
            }
        });
    }

    void updateComplete() {
        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                getContainingServer().removeProcessFromIndex(ProcessWrapper.this);
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.updateComplete called for " + process.getProcessId() + "("
                        + process.getVersion() + ")");
                process.setStatus(Status.RUN_OK);
                process.setRunCount(process.getRunCount() + 1);
                process.setAttempt(0);
                if (isPersistent)
                    processHome.update();
                _return(ProcessWrapper.this);
            }
        });
    }

    public boolean delete(User user, QueueOwner activeQueueOwner) {
        if (deleted)
            return false;

        if (!canDelete(user, activeQueueOwner))
            return false;

        return delete();
    }

    public boolean delete() {
        if (deleted)
            return false;

        doTransaction(new Callback() {

            @Override
            protected void doAction() {
                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                log.debug("ProcessWrapper.delete called for " + process.getProcessId() + "(" + process.getVersion()
                        + ")");
                if (isPersistent)
                    processHome.remove();
                getContainingServer().delete(ProcessWrapper.this);
                _return(ProcessWrapper.this);
            }
        }, new Callback<Void>() {

            @Override
            protected void doAction() {
                deleted = true;
            }
        });

        return true;
    }

    public boolean restart(User user, QueueOwner activeQueueOwner) {
        if (deleted)
            return false;

        if (!canRestart(user, activeQueueOwner))
            return false;

        return restart();
    }

    public boolean restart() {
        if (deleted)
            return false;

        if (updateRestart())
            return restart0();

        return false;
    }

    public void notifySelf() {
        ((ProcessImplServer) getContainingServer()).notifyProcess(this);
    }

    public void callListeners() {
        getParameters().iterateProcessListeners(new IterativeCallback<ProcessListener, Void>() {

            @Override
            protected void nextObject(ProcessListener l) {
                l.processUpdated(new Process<K>(ProcessWrapper.this));
            }
        });
    }

    private ProcessOutputable report_file = null;
    private String report_name = null;

    public boolean createReport() {
        return process.isAssociatedReport();
    }

    void setupOutputFile() {
        if (!createReport())
            return;

        report_file = process.getOutput().getOutputable(getQueueOwner(), getUserId(), getProcessName(),
                getReportType(), getSourceName());
        report_name = report_file.getOutputName();
    }

    public ProcessOutputable getProcessOutputable() {
        return report_file;
    }

    public boolean isSleeping() {
        synchronized (mutex) {
            return isNotRun() && processRunner != null && processRunner.isSleeping() && !isDeleted();
        }
    }

    public void notifyRunner() {
        synchronized (mutex) {
            if (isSleeping()) {
                processRunner.doNotify();
            }
        }
    }

    public void notifyRunnerForDelete() {
        synchronized (mutex) {
            if (isNotRun() && processRunner != null && processRunner.isSleeping()) {
                processRunner.doNotify();
            }
        }
    }

    public void interruptRunner() {
        rescheduleRequired = false;
        synchronized (mutex) {
            if ((isNotRun() && processRunner != null && processRunner.isSleeping())) {
                processRunner.doInterrupt();
            }
        }
    }

    boolean unPark(FilterableArrayList local_next_runners) {
        ProcessRunner _processRunner = processRunner;
        if (_processRunner == null) {
            getContainingServer().getProcessScheduler().unScheduleProcess(this);
            updateRunError();
            start();
        } else {
            if (!(_processRunner instanceof ProcessRunnerImpl)) {
                new QueujException("Process runner is not instance of ProcessRunnerImpl. key=" + getProcessKey());
                return false;
            }
            return ((ProcessRunnerImpl) _processRunner).unPark(local_next_runners);
        }
        return false;
    }

    private void doTransaction(Callback<Void> callback) {
        doTransaction(callback, null);
    }

    private void doTransaction(Callback<Void> callback, Callback<Void> commitCallback) {
        doTransaction(callback, commitCallback, false);
    }

    private void doTransaction(Callback<?> callback, Callback<Void> commitCallback, boolean doStart) {
        try {
            QueujTransaction transaction = QueujFactory.getTransaction();
            transaction.doTransaction(this, callback, commitCallback, doStart);
        } catch (ForceRollbackException fre) {
        } // Catch ForceRollbackException and swallow
    }

    //-------------------------------------------- Running the job --------------------------------------------

    static class ProcessRunStatus {
        private Integer result_code = new Integer(0);

        // a result code of 0 means normal success
        // a result code of 1 means a serious error, rollback handled here and error routine called
        // a positive result > 1 may to for a 'controlled' error, transaction is rolled back,
        //      but error routine NOT called (eg for validation error on imports)

        private boolean exception_thrown = false;
        private boolean run_error = false;
        private ForceRescheduleException force_reschedule_exception = null;
        private boolean force_complete = false;
    }

    void runProcess(ProcessRunner runner, boolean previousRunFailed) {
        final ProcessRunStatus run_status = new ProcessRunStatus();

        boolean first_section = true;

        final BatchProcessServer bps = getQueue().getBatchProcessServer();
        bps.resetSection(this, previousRunFailed);

        /**
         * optional pre process operations
         */
        getParameters().iteratePreProcesses(getSequencedProcessesIterativeCallback(run_status));

        /* Loop round all sections until a run_error occurs or no more sections exist.
         * A run_error consists of a non-zero return code or a thrown exception.
         * On a run_error a rollback occurs otherwise the transaction is committed.
         * If the previous run was a failure (is_failed) then the first section to run
         * is the one that was running when the failure occurred, otherwise the first
         * sction to run is the first section.
         */
        while (bps.hasMoreSections(this, first_section) && !run_status.run_error
                && run_status.force_reschedule_exception == null && !run_status.force_complete) {
            try {
                doTransaction(new Callback() {

                    @Override
                    protected void doAction() {
                        ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();

                        try {
                            run_status.result_code = bps.runProcess(ProcessWrapper.this, false);

                            if (!run_status.result_code.equals(BatchProcessServer.SUCCESS))
                                throw new ForceRollbackException();
                        } catch (ForceRescheduleException fre) {
                            handleRescheduleException(run_status, fre);
                        } catch (ForceProcessComplete fpc) {
                            run_status.force_complete = true;
                        } catch (RuntimeException re) {
                            checkForceReschedule(run_status, re);
                        }

                        if (isPersistent)
                            processHome.update();
                        _return(ProcessWrapper.this);
                    }
                });
            } catch (Exception e) {
                run_status.exception_thrown = true;
                bps.handleCustomRollback(this);
                new QueujException(e);
            } finally {
                first_section = false;

                // does not set run error if result code > 1
                run_status.run_error = run_status.exception_thrown
                        || run_status.result_code.equals(BatchProcessServer.FAILURE);
            }
        }

        /**
         * If a failure has occurred then run the failure section if one exists.
         * To run the failure section run_error must already be true so no need
         * to set it again if the failure section also fails!
         */
        if (run_status.run_error && bps.hasFailureSection(this)) {
            try {
                doTransaction(new Callback() {

                    @Override
                    protected void doAction() {
                        ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();

                        try {
                            Integer failure_result_code = bps.runProcess(ProcessWrapper.this, true);

                            if (failure_result_code.intValue() != 0)
                                throw new ForceRollbackException();
                        } catch (ForceRescheduleException fre) {
                            handleRescheduleException(run_status, fre);
                        } catch (ForceProcessComplete fpc) {
                            throw new QueujException("Cannot force complete from a failure section.");
                        } catch (RuntimeException re) {
                            checkForceReschedule(run_status, re);
                        }

                        if (isPersistent)
                            processHome.update();
                        _return(ProcessWrapper.this);
                    }
                });
            } catch (Exception e) {
                new QueujException(e);
            }
        }

        if (run_status.force_reschedule_exception != null)
            throw run_status.force_reschedule_exception;

        /**
         * optional post process operations (e.g. reloading BudgetingLogs after a budgeting run)
         * Post processes run whether the section run has failed or not. A flag is passed in with
         * the state of the section run, but it must be noted that the Process will still report
         * a status of running.
         */
        getParameters().iteratePostProcesses(getSequencedProcessesIterativeCallback(run_status));

        /**
         * Finally update the Process with the cummulative status thats been collected as the various parts
         * have run and/or failed.
         */
        try {
            doTransaction(new Callback() {

                @Override
                protected void doAction() {
                    getContainingServer().removeProcessFromIndex(ProcessWrapper.this);

                    ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();
                    process.setResultCode(run_status.result_code.intValue());

                    if (run_status.run_error) {
                        // Increase attempt first to prevent exception in higherPriorityJobExists
                        process.setAttempt(process.getAttempt() + 1);
                        process.setStatus(Status.RUN_ERROR);
                    }

                    getContainingServer().addProcessToIndex(ProcessWrapper.this);

                    if (isPersistent)
                        processHome.update();
                    _return(ProcessWrapper.this);
                }
            });
        } catch (Exception e) {
            new QueujException(e);
        }
    }

    private void handleRescheduleException(final ProcessRunStatus run_status, ForceRescheduleException fre) {
        run_status.force_reschedule_exception = fre;
        if (!fre.commitCurrentSection())
            throw new ForceRollbackException();
    }

    private IterativeCallback<SequencedProcess, Void> getSequencedProcessesIterativeCallback(
            final ProcessRunStatus run_status) {
        return new IterativeCallback<SequencedProcess, Void>() {
            @Override
            protected void nextObject(final SequencedProcess post_process) {
                try {
                    if (post_process.needsTransaction()) {
                        doTransaction(new Callback() {

                            @Override
                            protected void doAction() {
                                ProcessPersistence<ProcessEntity<K>, K> processHome = getProcessPersistence();

                                post_process.action(new Process<K>(ProcessWrapper.this), process.getUserId(),
                                        run_status.run_error);

                                if (isPersistent)
                                    processHome.update();
                                _return(ProcessWrapper.this);
                            }
                        });
                    } else
                        post_process.action(new Process<K>(ProcessWrapper.this), process.getUserId(),
                                run_status.run_error);
                } catch (Exception e) {
                    run_status.exception_thrown = true;
                    new QueujException(e);
                } finally {
                    run_status.run_error = run_status.exception_thrown || run_status.result_code.intValue() != 0;
                }
            }
        };
    }

    private void checkForceReschedule(ProcessRunStatus run_status, RuntimeException re) {
        Throwable t = re;
        while (t != null && !(t instanceof ForceRescheduleProvider) && !(t instanceof ForceRescheduleException))
            t = t.getCause();

        if (t != null && t instanceof ForceRescheduleProvider)
            handleRescheduleException(run_status, ((ForceRescheduleProvider) t).getRescheduleException());
        else if (t != null && t instanceof ForceRescheduleException)
            handleRescheduleException(run_status, (ForceRescheduleException) t);
        else
            throw re;
    }

    //-------------------------------------------- Starting the job --------------------------------------------

    final Object mutex = new Object();
    ProcessRunner processRunner = null;

    int runnerHashCode() {
        return processRunner != null ? processRunner.hashCode() : -1;
    }

    public void attach() {
        synchronized (mutex) {
            if (processRunner == null)
                return;

            while (processRunner != null) {
                try {
                    mutex.wait();
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    public boolean attach(long timeout) {
        final long end = System.currentTimeMillis() + timeout;
        synchronized (mutex) {
            if (processRunner == null)
                return true;

            long wait_timeout = end - System.currentTimeMillis();
            while (processRunner != null && wait_timeout > 0) {
                try {
                    mutex.wait(wait_timeout);
                } catch (InterruptedException ie) {
                }
                wait_timeout = end - System.currentTimeMillis();
            }

            return processRunner == null;
        }
    }

    public void start() {
        // Calculate next run time for the process
        GregorianCalendar nextRun = getNextRunTime();
        if (nextRun == null) // No run is due
            return;

        if (log.isDebugEnabled()) {
            String desc = getDescription();
            if (desc == null)
                desc = getProcessName();
            if (desc == null)
                desc = "Process: " + getProcessKey();
            desc = desc.trim();

            log.debug("Next run date for " + desc + " is " + nextRun.getTime().toString());
        }

        // Implementations can schedule the job themselves calling start(runTime, isFailed)
        // closer to the scheduled run time. This may allow the ProcessServer to be unloaded
        // from memory and re-loaded closer to the time. The default implementation
        // just returns false and does the scheduling in the ProcessScheduler.
        if (!getContainingServer().scheduleOverride(this, nextRun))
            start(nextRun, isFailed());
    }

    public void startNow() {
        start(new GregorianCalendar(), isFailed());
    }

    private boolean restart0() {
        return start(new GregorianCalendar(), true);
    }

    public boolean start(GregorianCalendar runTime, boolean isFailed) {
        synchronized (mutex) {
            if (processRunner != null)
                return false;

            processRunner = QueujFactory.getProcessRunner(this, runTime, isFailed);
            processRunner.doStart();
            return true;
        }
    }

    public void stop() {
        synchronized (mutex) {
            if (processRunner == null)
                return;

            processRunner.stop();
        }
    }
}