org.quartz.core.QuartzScheduler.java Source code

Java tutorial

Introduction

Here is the source code for org.quartz.core.QuartzScheduler.java

Source

/* 
 * Copyright 2001-2009 James House 
 * 
 * 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.
 * 
 */

/*
 * Previously Copyright (c) 2001-2004 James House
 */
package org.quartz.core;

import java.io.IOException;
import java.io.InputStream;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Calendar;
import org.quartz.InterruptableJob;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.JobPersistenceException;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;
import org.quartz.SchedulerContext;
import org.quartz.SchedulerException;
import org.quartz.SchedulerListener;
import org.quartz.listeners.SchedulerListenerSupport;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.quartz.UnableToInterruptJobException;
import org.quartz.impl.SchedulerRepository;
import org.quartz.simpl.SimpleJobFactory;
import org.quartz.spi.JobFactory;
import org.quartz.spi.SchedulerPlugin;
import org.quartz.spi.SchedulerSignaler;

/**
 * <p>
 * This is the heart of Quartz, an indirect implementation of the <code>{@link org.quartz.Scheduler}</code>
 * interface, containing methods to schedule <code>{@link org.quartz.Job}</code>s,
 * register <code>{@link org.quartz.JobListener}</code> instances, etc.
 * </p>// TODO: more docs...
 * 
 * @see org.quartz.Scheduler
 * @see org.quartz.core.QuartzSchedulerThread
 * @see org.quartz.spi.JobStore
 * @see org.quartz.spi.ThreadPool
 * 
 * @author James House
 */
public class QuartzScheduler implements RemotableQuartzScheduler {

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Constants.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    private static String VERSION_MAJOR = "UNKNOWN";
    private static String VERSION_MINOR = "UNKNOWN";
    private static String VERSION_ITERATION = "UNKNOWN";

    static {
        Properties props = new Properties();
        InputStream is = null;
        try {
            is = QuartzScheduler.class.getResourceAsStream("/build.properties");
            if (is != null) {
                props.load(is);
                VERSION_MAJOR = props.getProperty("version.major");
                VERSION_MINOR = props.getProperty("version.minor");
                VERSION_ITERATION = props.getProperty("version.iter");
            }
        } catch (IOException e) {
            (LogFactory.getLog(QuartzScheduler.class)).error("Error loading version info from build.properties.",
                    e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception ignore) {
                }
            }
        }

    }

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Data members.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    private QuartzSchedulerResources resources;

    private QuartzSchedulerThread schedThread;

    private ThreadGroup threadGroup;

    private SchedulerContext context = new SchedulerContext();

    private HashMap jobListeners = new HashMap(10);

    private HashMap globalJobListeners = new HashMap(10);

    private HashMap triggerListeners = new HashMap(10);

    private HashMap globalTriggerListeners = new HashMap(10);

    private ArrayList schedulerListeners = new ArrayList(10);

    private JobFactory jobFactory = new SimpleJobFactory();

    ExecutingJobsManager jobMgr = null;

    ErrorLogger errLogger = null;

    private SchedulerSignaler signaler;

    private Random random = new Random();

    private ArrayList holdToPreventGC = new ArrayList(5);

    private boolean signalOnSchedulingChange = true;

    private boolean closed = false;
    private boolean shuttingDown = false;

    private Date initialStart = null;

    private final Log log = LogFactory.getLog(getClass());

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Constructors.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    /**
     * <p>
     * Create a <code>QuartzScheduler</code> with the given configuration
     * properties.
     * </p>
     * 
     * @see QuartzSchedulerResources
     */
    public QuartzScheduler(QuartzSchedulerResources resources, SchedulingContext ctxt, long idleWaitTime,
            long dbRetryInterval) throws SchedulerException {
        this.resources = resources;
        try {
            bind();
        } catch (Exception re) {
            throw new SchedulerException("Unable to bind scheduler to RMI Registry.", re);
        }

        if (resources.getJMXExport()) {
            try {
                registerJMX();
            } catch (Exception e) {
                throw new SchedulerException("Unable to register scheduler with MBeanServer.", e);
            }
        }

        this.schedThread = new QuartzSchedulerThread(this, resources, ctxt);
        if (idleWaitTime > 0) {
            this.schedThread.setIdleWaitTime(idleWaitTime);
        }
        if (dbRetryInterval > 0) {
            this.schedThread.setDbFailureRetryInterval(dbRetryInterval);
        }

        jobMgr = new ExecutingJobsManager();
        addGlobalJobListener(jobMgr);
        errLogger = new ErrorLogger();
        addSchedulerListener(errLogger);

        signaler = new SchedulerSignalerImpl(this, this.schedThread);

        getLog().info("Quartz Scheduler v." + getVersion() + " created.");
    }

    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Interface.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    public String getVersion() {
        return getVersionMajor() + "." + getVersionMinor() + "." + getVersionIteration();
    }

    public static String getVersionMajor() {
        return VERSION_MAJOR;
    }

    public static String getVersionMinor() {
        return VERSION_MINOR;
    }

    public static String getVersionIteration() {
        return VERSION_ITERATION;
    }

    public SchedulerSignaler getSchedulerSignaler() {
        return signaler;
    }

    public Log getLog() {
        return log;
    }

    /**
     * Register the scheduler in the local MBeanServer.
     */
    private void registerJMX() throws Exception {
        org.apache.commons.modeler.Registry registry = org.apache.commons.modeler.Registry.getRegistry(null, null);

        String jmxObjectName = resources.getJMXObjectName();

        registry.registerComponent(this, jmxObjectName, null);

        getLog().info("Scheduler registered with local MBeanServer under name '" + jmxObjectName + "'");
    }

    /**
     * Unregister the scheduler from the local MBeanServer.
     */
    private void unregisterJMX() throws Exception {
        org.apache.commons.modeler.Registry registry = org.apache.commons.modeler.Registry.getRegistry(null, null);

        String jmxObjectName = resources.getJMXObjectName();

        registry.unregisterComponent(jmxObjectName);

        getLog().info("Scheduler unregistered from name '" + jmxObjectName + "' in the local MBeanServer.");
    }

    /**
     * <p>
     * Bind the scheduler to an RMI registry.
     * </p>
     */
    private void bind() throws RemoteException {
        String host = resources.getRMIRegistryHost();
        // don't export if we're not configured to do so...
        if (host == null || host.length() == 0) {
            return;
        }

        RemotableQuartzScheduler exportable = null;

        if (resources.getRMIServerPort() > 0) {
            exportable = (RemotableQuartzScheduler) UnicastRemoteObject.exportObject(this,
                    resources.getRMIServerPort());
        } else {
            exportable = (RemotableQuartzScheduler) UnicastRemoteObject.exportObject(this);
        }

        Registry registry = null;

        if (resources.getRMICreateRegistryStrategy().equals(QuartzSchedulerResources.CREATE_REGISTRY_AS_NEEDED)) {
            try {
                // First try to get an existing one, instead of creating it,
                // since if
                // we're in a web-app being 'hot' re-depoloyed, then the JVM
                // still
                // has the registry that we created above the first time...
                registry = LocateRegistry.getRegistry(resources.getRMIRegistryPort());
                registry.list();
            } catch (Exception e) {
                registry = LocateRegistry.createRegistry(resources.getRMIRegistryPort());
            }
        } else if (resources.getRMICreateRegistryStrategy()
                .equals(QuartzSchedulerResources.CREATE_REGISTRY_ALWAYS)) {
            try {
                registry = LocateRegistry.createRegistry(resources.getRMIRegistryPort());
            } catch (Exception e) {
                // Fall back to an existing one, instead of creating it, since
                // if
                // we're in a web-app being 'hot' re-depoloyed, then the JVM
                // still
                // has the registry that we created above the first time...
                registry = LocateRegistry.getRegistry(resources.getRMIRegistryPort());
            }
        } else {
            registry = LocateRegistry.getRegistry(resources.getRMIRegistryHost(), resources.getRMIRegistryPort());
        }

        String bindName = resources.getRMIBindName();

        registry.rebind(bindName, exportable);

        getLog().info("Scheduler bound to RMI registry under name '" + bindName + "'");
    }

    /**
     * <p>
     * Un-bind the scheduler from an RMI registry.
     * </p>
     */
    private void unBind() throws RemoteException {
        String host = resources.getRMIRegistryHost();
        // don't un-export if we're not configured to do so...
        if (host == null || host.length() == 0) {
            return;
        }

        Registry registry = LocateRegistry.getRegistry(resources.getRMIRegistryHost(),
                resources.getRMIRegistryPort());

        String bindName = resources.getRMIBindName();

        try {
            registry.unbind(bindName);
            UnicastRemoteObject.unexportObject(this, true);
        } catch (java.rmi.NotBoundException nbe) {
        }

        getLog().info("Scheduler un-bound from name '" + bindName + "' in RMI registry");
    }

    /**
     * <p>
     * Returns the name of the <code>QuartzScheduler</code>.
     * </p>
     */
    public String getSchedulerName() {
        return resources.getName();
    }

    /**
     * <p>
     * Returns the instance Id of the <code>QuartzScheduler</code>.
     * </p>
     */
    public String getSchedulerInstanceId() {
        return resources.getInstanceId();
    }

    /**
     * <p>
     * Returns the name of the <code>QuartzScheduler</code>.
     * </p>
     */
    public ThreadGroup getSchedulerThreadGroup() {
        if (threadGroup == null) {
            threadGroup = new ThreadGroup("QuartzScheduler:" + getSchedulerName());
            if (resources.getMakeSchedulerThreadDaemon()) {
                threadGroup.setDaemon(true);
            }
        }

        return threadGroup;
    }

    public void addNoGCObject(Object obj) {
        holdToPreventGC.add(obj);
    }

    public boolean removeNoGCObject(Object obj) {
        return holdToPreventGC.remove(obj);
    }

    /**
     * <p>
     * Returns the <code>SchedulerContext</code> of the <code>Scheduler</code>.
     * </p>
     */
    public SchedulerContext getSchedulerContext() throws SchedulerException {
        return context;
    }

    public boolean isSignalOnSchedulingChange() {
        return signalOnSchedulingChange;
    }

    public void setSignalOnSchedulingChange(boolean signalOnSchedulingChange) {
        this.signalOnSchedulingChange = signalOnSchedulingChange;
    }

    ///////////////////////////////////////////////////////////////////////////
    ///
    /// Schedululer State Management Methods
    ///
    ///////////////////////////////////////////////////////////////////////////

    /**
     * <p>
     * Starts the <code>QuartzScheduler</code>'s threads that fire <code>{@link org.quartz.Trigger}s</code>.
     * </p>
     * 
     * <p>
     * All <code>{@link org.quartz.Trigger}s</code> that have misfired will
     * be passed to the appropriate TriggerListener(s).
     * </p>
     */
    public void start() throws SchedulerException {

        if (shuttingDown || closed) {
            throw new SchedulerException("The Scheduler cannot be restarted after shutdown() has been called.");
        }

        if (initialStart == null) {
            initialStart = new Date();
            this.resources.getJobStore().schedulerStarted();
            startPlugins();
        }

        schedThread.togglePause(false);

        getLog().info("Scheduler " + resources.getUniqueIdentifier() + " started.");
    }

    public void startDelayed(final int seconds) throws SchedulerException {
        if (shuttingDown || closed) {
            throw new SchedulerException("The Scheduler cannot be restarted after shutdown() has been called.");
        }

        Thread t = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(seconds * 1000L);
                } catch (InterruptedException ignore) {
                }
                try {
                    start();
                } catch (SchedulerException se) {
                    getLog().error("Unable to start secheduler after startup delay.", se);
                }
            }
        });
        t.start();
    }

    /**
     * <p>
     * Temporarily halts the <code>QuartzScheduler</code>'s firing of <code>{@link org.quartz.Trigger}s</code>.
     * </p>
     * 
     * <p>
     * The scheduler is not destroyed, and can be re-started at any time.
     * </p>
     */
    public void standby() {
        schedThread.togglePause(true);
        getLog().info("Scheduler " + resources.getUniqueIdentifier() + " paused.");
    }

    /**
     * <p>
     * Reports whether the <code>Scheduler</code> is paused.
     * </p>
     */
    public boolean isInStandbyMode() {
        return schedThread.isPaused();
    }

    public Date runningSince() {
        return initialStart;
    }

    public int numJobsExecuted() {
        return jobMgr.getNumJobsFired();
    }

    public Class getJobStoreClass() {
        return resources.getJobStore().getClass();
    }

    public boolean supportsPersistence() {
        return resources.getJobStore().supportsPersistence();
    }

    public Class getThreadPoolClass() {
        return resources.getThreadPool().getClass();
    }

    public int getThreadPoolSize() {
        return resources.getThreadPool().getPoolSize();
    }

    /**
     * <p>
     * Halts the <code>QuartzScheduler</code>'s firing of <code>{@link org.quartz.Trigger}s</code>,
     * and cleans up all resources associated with the QuartzScheduler.
     * Equivalent to <code>shutdown(false)</code>.
     * </p>
     * 
     * <p>
     * The scheduler cannot be re-started.
     * </p>
     */
    public void shutdown() {
        shutdown(false);
    }

    /**
     * <p>
     * Halts the <code>QuartzScheduler</code>'s firing of <code>{@link org.quartz.Trigger}s</code>,
     * and cleans up all resources associated with the QuartzScheduler.
     * </p>
     * 
     * <p>
     * The scheduler cannot be re-started.
     * </p>
     * 
     * @param waitForJobsToComplete
     *          if <code>true</code> the scheduler will not allow this method
     *          to return until all currently executing jobs have completed.
     */
    public void shutdown(boolean waitForJobsToComplete) {

        if (shuttingDown || closed) {
            return;
        }

        shuttingDown = true;

        getLog().info("Scheduler " + resources.getUniqueIdentifier() + " shutting down.");
        standby();

        schedThread.halt();

        resources.getThreadPool().shutdown(waitForJobsToComplete);

        if (waitForJobsToComplete) {
            while (jobMgr.getNumJobsCurrentlyExecuting() > 0) {
                try {
                    Thread.sleep(100);
                } catch (Exception ignore) {
                }
            }
        }

        // Scheduler thread may have be waiting for the fire time of an acquired 
        // trigger and need time to release the trigger once halted, so make sure
        // the thread is dead before continuing to shutdown the job store.
        try {
            schedThread.join();
        } catch (InterruptedException ignore) {
        }

        closed = true;

        resources.getJobStore().shutdown();

        notifySchedulerListenersShutdown();

        shutdownPlugins();

        SchedulerRepository.getInstance().remove(resources.getName());

        holdToPreventGC.clear();

        try {
            unBind();
        } catch (RemoteException re) {
        }

        if (resources.getJMXExport()) {
            try {
                unregisterJMX();
            } catch (Exception e) {
            }
        }

        getLog().info("Scheduler " + resources.getUniqueIdentifier() + " shutdown complete.");
    }

    /**
     * <p>
     * Reports whether the <code>Scheduler</code> has been shutdown.
     * </p>
     */
    public boolean isShutdown() {
        return closed;
    }

    public void validateState() throws SchedulerException {
        if (isShutdown()) {
            throw new SchedulerException("The Scheduler has been shutdown.");
        }

        // other conditions to check (?)
    }

    /**
     * <p>
     * Return a list of <code>JobExecutionContext</code> objects that
     * represent all currently executing Jobs in this Scheduler instance.
     * </p>
     * 
     * <p>
     * This method is not cluster aware.  That is, it will only return Jobs
     * currently executing in this Scheduler instance, not across the entire
     * cluster.
     * </p>
     * 
     * <p>
     * Note that the list returned is an 'instantaneous' snap-shot, and that as
     * soon as it's returned, the true list of executing jobs may be different.
     * </p>
     */
    public List getCurrentlyExecutingJobs() {
        return jobMgr.getExecutingJobs();
    }

    ///////////////////////////////////////////////////////////////////////////
    ///
    /// Scheduling-related Methods
    ///
    ///////////////////////////////////////////////////////////////////////////

    /**
     * <p>
     * Add the <code>{@link org.quartz.Job}</code> identified by the given
     * <code>{@link org.quartz.JobDetail}</code> to the Scheduler, and
     * associate the given <code>{@link org.quartz.Trigger}</code> with it.
     * </p>
     * 
     * <p>
     * If the given Trigger does not reference any <code>Job</code>, then it
     * will be set to reference the Job passed with it into this method.
     * </p>
     * 
     * @throws SchedulerException
     *           if the Job or Trigger cannot be added to the Scheduler, or
     *           there is an internal Scheduler error.
     */
    public Date scheduleJob(SchedulingContext ctxt, JobDetail jobDetail, Trigger trigger)
            throws SchedulerException {
        validateState();

        if (jobDetail == null) {
            throw new SchedulerException("JobDetail cannot be null", SchedulerException.ERR_CLIENT_ERROR);
        }

        if (trigger == null) {
            throw new SchedulerException("Trigger cannot be null", SchedulerException.ERR_CLIENT_ERROR);
        }

        jobDetail.validate();

        if (trigger.getJobName() == null) {
            trigger.setJobName(jobDetail.getName());
            trigger.setJobGroup(jobDetail.getGroup());
        } else if (trigger.getJobName() != null && !trigger.getJobName().equals(jobDetail.getName())) {
            throw new SchedulerException("Trigger does not reference given job!",
                    SchedulerException.ERR_CLIENT_ERROR);
        } else if (trigger.getJobGroup() != null && !trigger.getJobGroup().equals(jobDetail.getGroup())) {
            throw new SchedulerException("Trigger does not reference given job!",
                    SchedulerException.ERR_CLIENT_ERROR);
        }

        trigger.validate();

        Calendar cal = null;
        if (trigger.getCalendarName() != null) {
            cal = resources.getJobStore().retrieveCalendar(ctxt, trigger.getCalendarName());
        }
        Date ft = trigger.computeFirstFireTime(cal);

        if (ft == null) {
            throw new SchedulerException("Based on configured schedule, the given trigger will never fire.",
                    SchedulerException.ERR_CLIENT_ERROR);
        }

        resources.getJobStore().storeJobAndTrigger(ctxt, jobDetail, trigger);
        notifySchedulerThread(trigger.getNextFireTime().getTime());
        notifySchedulerListenersSchduled(trigger);

        return ft;
    }

    /**
     * <p>
     * Schedule the given <code>{@link org.quartz.Trigger}</code> with the
     * <code>Job</code> identified by the <code>Trigger</code>'s settings.
     * </p>
     * 
     * @throws SchedulerException
     *           if the indicated Job does not exist, or the Trigger cannot be
     *           added to the Scheduler, or there is an internal Scheduler
     *           error.
     */
    public Date scheduleJob(SchedulingContext ctxt, Trigger trigger) throws SchedulerException {
        validateState();

        if (trigger == null) {
            throw new SchedulerException("Trigger cannot be null", SchedulerException.ERR_CLIENT_ERROR);
        }

        trigger.validate();

        Calendar cal = null;
        if (trigger.getCalendarName() != null) {
            cal = resources.getJobStore().retrieveCalendar(ctxt, trigger.getCalendarName());
            if (cal == null) {
                throw new SchedulerException("Calendar not found: " + trigger.getCalendarName(),
                        SchedulerException.ERR_PERSISTENCE_CALENDAR_DOES_NOT_EXIST);
            }
        }
        Date ft = trigger.computeFirstFireTime(cal);

        if (ft == null) {
            throw new SchedulerException("Based on configured schedule, the given trigger will never fire.",
                    SchedulerException.ERR_CLIENT_ERROR);
        }

        resources.getJobStore().storeTrigger(ctxt, trigger, false);
        notifySchedulerThread(trigger.getNextFireTime().getTime());
        notifySchedulerListenersSchduled(trigger);

        return ft;
    }

    /**
     * <p>
     * Add the given <code>Job</code> to the Scheduler - with no associated
     * <code>Trigger</code>. The <code>Job</code> will be 'dormant' until
     * it is scheduled with a <code>Trigger</code>, or <code>Scheduler.triggerJob()</code>
     * is called for it.
     * </p>
     * 
     * <p>
     * The <code>Job</code> must by definition be 'durable', if it is not,
     * SchedulerException will be thrown.
     * </p>
     * 
     * @throws SchedulerException
     *           if there is an internal Scheduler error, or if the Job is not
     *           durable, or a Job with the same name already exists, and
     *           <code>replace</code> is <code>false</code>.
     */
    public void addJob(SchedulingContext ctxt, JobDetail jobDetail, boolean replace) throws SchedulerException {
        validateState();

        if (!jobDetail.isDurable() && !replace) {
            throw new SchedulerException("Jobs added with no trigger must be durable.",
                    SchedulerException.ERR_CLIENT_ERROR);
        }

        resources.getJobStore().storeJob(ctxt, jobDetail, replace);
    }

    /**
     * <p>
     * Delete the identified <code>Job</code> from the Scheduler - and any
     * associated <code>Trigger</code>s.
     * </p>
     * 
     * @return true if the Job was found and deleted.
     * @throws SchedulerException
     *           if there is an internal Scheduler error.
     */
    public boolean deleteJob(SchedulingContext ctxt, String jobName, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().removeJob(ctxt, jobName, groupName);
    }

    /**
     * <p>
     * Remove the indicated <code>{@link org.quartz.Trigger}</code> from the
     * scheduler.
     * </p>
     */
    public boolean unscheduleJob(SchedulingContext ctxt, String triggerName, String groupName)
            throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        if (resources.getJobStore().removeTrigger(ctxt, triggerName, groupName)) {
            notifySchedulerThread(0L);
            notifySchedulerListenersUnschduled(triggerName, groupName);
        } else {
            return false;
        }

        return true;
    }

    /**
     * <p>
     * Remove (delete) the <code>{@link org.quartz.Trigger}</code> with the
     * given name, and store the new given one - which must be associated
     * with the same job.
     * </p>
     * 
     * @param triggerName
     *          The name of the <code>Trigger</code> to be removed.
     * @param groupName
     *          The group name of the <code>Trigger</code> to be removed.
     * @param newTrigger
     *          The new <code>Trigger</code> to be stored.
     * @return <code>null</code> if a <code>Trigger</code> with the given
     *         name & group was not found and removed from the store, otherwise
     *         the first fire time of the newly scheduled trigger.
     */
    public Date rescheduleJob(SchedulingContext ctxt, String triggerName, String groupName, Trigger newTrigger)
            throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        newTrigger.validate();

        Calendar cal = null;
        if (newTrigger.getCalendarName() != null) {
            cal = resources.getJobStore().retrieveCalendar(ctxt, newTrigger.getCalendarName());
        }
        Date ft = newTrigger.computeFirstFireTime(cal);

        if (ft == null) {
            throw new SchedulerException("Based on configured schedule, the given trigger will never fire.",
                    SchedulerException.ERR_CLIENT_ERROR);
        }

        if (resources.getJobStore().replaceTrigger(ctxt, triggerName, groupName, newTrigger)) {
            notifySchedulerThread(newTrigger.getNextFireTime().getTime());
            notifySchedulerListenersUnschduled(triggerName, groupName);
            notifySchedulerListenersSchduled(newTrigger);
        } else {
            return null;
        }

        return ft;

    }

    private String newTriggerId() {
        long r = random.nextLong();
        if (r < 0) {
            r = -r;
        }
        return "MT_" + Long.toString(r, 30 + (int) (System.currentTimeMillis() % 7));
    }

    /**
     * <p>
     * Trigger the identified <code>{@link org.quartz.Job}</code> (execute it
     * now) - with a non-volatile trigger.
     * </p>
     */
    public void triggerJob(SchedulingContext ctxt, String jobName, String groupName, JobDataMap data)
            throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        Trigger trig = new org.quartz.SimpleTrigger(newTriggerId(), Scheduler.DEFAULT_MANUAL_TRIGGERS, jobName,
                groupName, new Date(), null, 0, 0);
        trig.setVolatility(false);
        trig.computeFirstFireTime(null);
        if (data != null) {
            trig.setJobDataMap(data);
        }

        boolean collision = true;
        while (collision) {
            try {
                resources.getJobStore().storeTrigger(ctxt, trig, false);
                collision = false;
            } catch (ObjectAlreadyExistsException oaee) {
                trig.setName(newTriggerId());
            }
        }

        notifySchedulerThread(trig.getNextFireTime().getTime());
        notifySchedulerListenersSchduled(trig);
    }

    /**
     * <p>
     * Trigger the identified <code>{@link org.quartz.Job}</code> (execute it
     * now) - with a volatile trigger.
     * </p>
     */
    public void triggerJobWithVolatileTrigger(SchedulingContext ctxt, String jobName, String groupName,
            JobDataMap data) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        Trigger trig = new org.quartz.SimpleTrigger(newTriggerId(), Scheduler.DEFAULT_MANUAL_TRIGGERS, jobName,
                groupName, new Date(), null, 0, 0);
        trig.setVolatility(true);
        trig.computeFirstFireTime(null);
        if (data != null) {
            trig.setJobDataMap(data);
        }

        boolean collision = true;
        while (collision) {
            try {
                resources.getJobStore().storeTrigger(ctxt, trig, false);
                collision = false;
            } catch (ObjectAlreadyExistsException oaee) {
                trig.setName(newTriggerId());
            }
        }

        notifySchedulerThread(trig.getNextFireTime().getTime());
        notifySchedulerListenersSchduled(trig);
    }

    /**
     * <p>
     * Pause the <code>{@link Trigger}</code> with the given name.
     * </p>
     *  
     */
    public void pauseTrigger(SchedulingContext ctxt, String triggerName, String groupName)
            throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().pauseTrigger(ctxt, triggerName, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersPausedTrigger(triggerName, groupName);
    }

    /**
     * <p>
     * Pause all of the <code>{@link Trigger}s</code> in the given group.
     * </p>
     *  
     */
    public void pauseTriggerGroup(SchedulingContext ctxt, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().pauseTriggerGroup(ctxt, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersPausedTrigger(null, groupName);
    }

    /**
     * <p>
     * Pause the <code>{@link org.quartz.JobDetail}</code> with the given
     * name - by pausing all of its current <code>Trigger</code>s.
     * </p>
     *  
     */
    public void pauseJob(SchedulingContext ctxt, String jobName, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().pauseJob(ctxt, jobName, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersPausedJob(jobName, groupName);
    }

    /**
     * <p>
     * Pause all of the <code>{@link org.quartz.JobDetail}s</code> in the
     * given group - by pausing all of their <code>Trigger</code>s.
     * </p>
     *  
     */
    public void pauseJobGroup(SchedulingContext ctxt, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().pauseJobGroup(ctxt, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersPausedJob(null, groupName);
    }

    /**
     * <p>
     * Resume (un-pause) the <code>{@link Trigger}</code> with the given
     * name.
     * </p>
     * 
     * <p>
     * If the <code>Trigger</code> missed one or more fire-times, then the
     * <code>Trigger</code>'s misfire instruction will be applied.
     * </p>
     *  
     */
    public void resumeTrigger(SchedulingContext ctxt, String triggerName, String groupName)
            throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().resumeTrigger(ctxt, triggerName, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersResumedTrigger(triggerName, groupName);
    }

    /**
     * <p>
     * Resume (un-pause) all of the <code>{@link Trigger}s</code> in the
     * given group.
     * </p>
     * 
     * <p>
     * If any <code>Trigger</code> missed one or more fire-times, then the
     * <code>Trigger</code>'s misfire instruction will be applied.
     * </p>
     *  
     */
    public void resumeTriggerGroup(SchedulingContext ctxt, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().resumeTriggerGroup(ctxt, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersResumedTrigger(null, groupName);
    }

    public Set getPausedTriggerGroups(SchedulingContext ctxt) throws SchedulerException {
        return resources.getJobStore().getPausedTriggerGroups(ctxt);
    }

    /**
     * <p>
     * Resume (un-pause) the <code>{@link org.quartz.JobDetail}</code> with
     * the given name.
     * </p>
     * 
     * <p>
     * If any of the <code>Job</code>'s<code>Trigger</code> s missed one
     * or more fire-times, then the <code>Trigger</code>'s misfire
     * instruction will be applied.
     * </p>
     *  
     */
    public void resumeJob(SchedulingContext ctxt, String jobName, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().resumeJob(ctxt, jobName, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersResumedJob(jobName, groupName);
    }

    /**
     * <p>
     * Resume (un-pause) all of the <code>{@link org.quartz.JobDetail}s</code>
     * in the given group.
     * </p>
     * 
     * <p>
     * If any of the <code>Job</code> s had <code>Trigger</code> s that
     * missed one or more fire-times, then the <code>Trigger</code>'s
     * misfire instruction will be applied.
     * </p>
     *  
     */
    public void resumeJobGroup(SchedulingContext ctxt, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        resources.getJobStore().resumeJobGroup(ctxt, groupName);
        notifySchedulerThread(0L);
        notifySchedulerListenersResumedJob(null, groupName);
    }

    /**
     * <p>
     * Pause all triggers - equivalent of calling <code>pauseTriggerGroup(group)</code>
     * on every group.
     * </p>
     * 
     * <p>
     * When <code>resumeAll()</code> is called (to un-pause), trigger misfire
     * instructions WILL be applied.
     * </p>
     * 
     * @see #resumeAll(SchedulingContext)
     * @see #pauseTriggerGroup(SchedulingContext, String)
     * @see #standby()
     */
    public void pauseAll(SchedulingContext ctxt) throws SchedulerException {
        validateState();

        resources.getJobStore().pauseAll(ctxt);
        notifySchedulerThread(0L);
        notifySchedulerListenersPausedTrigger(null, null);
    }

    /**
     * <p>
     * Resume (un-pause) all triggers - equivalent of calling <code>resumeTriggerGroup(group)</code>
     * on every group.
     * </p>
     * 
     * <p>
     * If any <code>Trigger</code> missed one or more fire-times, then the
     * <code>Trigger</code>'s misfire instruction will be applied.
     * </p>
     * 
     * @see #pauseAll(SchedulingContext)
     */
    public void resumeAll(SchedulingContext ctxt) throws SchedulerException {
        validateState();

        resources.getJobStore().resumeAll(ctxt);
        notifySchedulerThread(0L);
        notifySchedulerListenersResumedTrigger(null, null);
    }

    /**
     * <p>
     * Get the names of all known <code>{@link org.quartz.Job}</code> groups.
     * </p>
     */
    public String[] getJobGroupNames(SchedulingContext ctxt) throws SchedulerException {
        validateState();

        return resources.getJobStore().getJobGroupNames(ctxt);
    }

    /**
     * <p>
     * Get the names of all the <code>{@link org.quartz.Job}s</code> in the
     * given group.
     * </p>
     */
    public String[] getJobNames(SchedulingContext ctxt, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().getJobNames(ctxt, groupName);
    }

    /**
     * <p>
     * Get all <code>{@link Trigger}</code> s that are associated with the
     * identified <code>{@link org.quartz.JobDetail}</code>.
     * </p>
     */
    public Trigger[] getTriggersOfJob(SchedulingContext ctxt, String jobName, String groupName)
            throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().getTriggersForJob(ctxt, jobName, groupName);
    }

    /**
     * <p>
     * Get the names of all known <code>{@link org.quartz.Trigger}</code>
     * groups.
     * </p>
     */
    public String[] getTriggerGroupNames(SchedulingContext ctxt) throws SchedulerException {
        validateState();

        return resources.getJobStore().getTriggerGroupNames(ctxt);
    }

    /**
     * <p>
     * Get the names of all the <code>{@link org.quartz.Trigger}s</code> in
     * the given group.
     * </p>
     */
    public String[] getTriggerNames(SchedulingContext ctxt, String groupName) throws SchedulerException {
        validateState();

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().getTriggerNames(ctxt, groupName);
    }

    /**
     * <p>
     * Get the <code>{@link JobDetail}</code> for the <code>Job</code>
     * instance with the given name and group.
     * </p>
     */
    public JobDetail getJobDetail(SchedulingContext ctxt, String jobName, String jobGroup)
            throws SchedulerException {
        validateState();

        if (jobGroup == null) {
            jobGroup = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().retrieveJob(ctxt, jobName, jobGroup);
    }

    /**
     * <p>
     * Get the <code>{@link Trigger}</code> instance with the given name and
     * group.
     * </p>
     */
    public Trigger getTrigger(SchedulingContext ctxt, String triggerName, String triggerGroup)
            throws SchedulerException {
        validateState();

        if (triggerGroup == null) {
            triggerGroup = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().retrieveTrigger(ctxt, triggerName, triggerGroup);
    }

    /**
     * <p>
     * Get the current state of the identified <code>{@link Trigger}</code>.
     * </p>
     * 
     * @see Trigger#STATE_NORMAL
     * @see Trigger#STATE_PAUSED
     * @see Trigger#STATE_COMPLETE
     * @see Trigger#STATE_ERROR
     */
    public int getTriggerState(SchedulingContext ctxt, String triggerName, String triggerGroup)
            throws SchedulerException {
        validateState();

        if (triggerGroup == null) {
            triggerGroup = Scheduler.DEFAULT_GROUP;
        }

        return resources.getJobStore().getTriggerState(ctxt, triggerName, triggerGroup);
    }

    /**
     * <p>
     * Add (register) the given <code>Calendar</code> to the Scheduler.
     * </p>
     * 
     * @throws SchedulerException
     *           if there is an internal Scheduler error, or a Calendar with
     *           the same name already exists, and <code>replace</code> is
     *           <code>false</code>.
     */
    public void addCalendar(SchedulingContext ctxt, String calName, Calendar calendar, boolean replace,
            boolean updateTriggers) throws SchedulerException {
        validateState();

        resources.getJobStore().storeCalendar(ctxt, calName, calendar, replace, updateTriggers);
    }

    /**
     * <p>
     * Delete the identified <code>Calendar</code> from the Scheduler.
     * </p>
     * 
     * @return true if the Calendar was found and deleted.
     * @throws SchedulerException
     *           if there is an internal Scheduler error.
     */
    public boolean deleteCalendar(SchedulingContext ctxt, String calName) throws SchedulerException {
        validateState();

        return resources.getJobStore().removeCalendar(ctxt, calName);
    }

    /**
     * <p>
     * Get the <code>{@link Calendar}</code> instance with the given name.
     * </p>
     */
    public Calendar getCalendar(SchedulingContext ctxt, String calName) throws SchedulerException {
        validateState();

        return resources.getJobStore().retrieveCalendar(ctxt, calName);
    }

    /**
     * <p>
     * Get the names of all registered <code>{@link Calendar}s</code>.
     * </p>
     */
    public String[] getCalendarNames(SchedulingContext ctxt) throws SchedulerException {
        validateState();

        return resources.getJobStore().getCalendarNames(ctxt);
    }

    /**
     * <p>
     * Add the given <code>{@link org.quartz.JobListener}</code> to the
     * <code>Scheduler</code>'s<i>global</i> list.
     * </p>
     * 
     * <p>
     * Listeners in the 'global' list receive notification of execution events
     * for ALL <code>{@link org.quartz.Job}</code>s.
     * </p>
     */
    public void addGlobalJobListener(JobListener jobListener) {
        if (jobListener.getName() == null || jobListener.getName().length() == 0) {
            throw new IllegalArgumentException("JobListener name cannot be empty.");
        }

        synchronized (globalJobListeners) {
            globalJobListeners.put(jobListener.getName(), jobListener);
        }
    }

    /**
     * <p>
     * Add the given <code>{@link org.quartz.JobListener}</code> to the
     * <code>Scheduler</code>'s list, of registered <code>JobListener</code>s.
     */
    public void addJobListener(JobListener jobListener) {
        if (jobListener.getName() == null || jobListener.getName().length() == 0) {
            throw new IllegalArgumentException("JobListener name cannot be empty.");
        }

        synchronized (jobListeners) {
            jobListeners.put(jobListener.getName(), jobListener);
        }
    }

    /**
     * <p>
     * Remove the given <code>{@link org.quartz.JobListener}</code> from the
     * <code>Scheduler</code>'s list of <i>global</i> listeners.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     *         
     * @deprecated Use <code>{@link #removeGlobalJobListener(String)}</code>
     */
    public boolean removeGlobalJobListener(JobListener jobListener) {
        return removeGlobalJobListener((jobListener == null) ? null : jobListener.getName());
    }

    /**
     * <p>
     * Remove the identifed <code>{@link JobListener}</code> from the <code>Scheduler</code>'s
     * list of <i>global</i> listeners.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     */
    public boolean removeGlobalJobListener(String name) {
        synchronized (globalJobListeners) {
            return (globalJobListeners.remove(name) != null);
        }
    }

    /**
     * <p>
     * Remove the identifed <code>{@link org.quartz.JobListener}</code> from
     * the <code>Scheduler</code>'s list of registered listeners.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     */
    public boolean removeJobListener(String name) {
        synchronized (jobListeners) {
            return (jobListeners.remove(name) != null);
        }
    }

    /**
     * <p>
     * Get a List containing all of the <code>{@link org.quartz.JobListener}</code>
     * s in the <code>Scheduler</code>'s<i>global</i> list.
     * </p>
     */
    public List getGlobalJobListeners() {
        synchronized (globalJobListeners) {
            return new LinkedList(globalJobListeners.values());
        }
    }

    /**
     * <p>
     * Get a Set containing the names of all the <i>non-global</i><code>{@link org.quartz.JobListener}</code>
     * s registered with the <code>Scheduler</code>.
     * </p>
     */
    public Set getJobListenerNames() {
        synchronized (jobListeners) {
            return new HashSet(jobListeners.keySet());
        }
    }

    /**
     * <p>
     * Get the <i>global</i><code>{@link org.quartz.JobListener}</code>
     * that has the given name.
     * </p>
     */
    public JobListener getGlobalJobListener(String name) {
        synchronized (globalJobListeners) {
            return (JobListener) globalJobListeners.get(name);
        }
    }

    /**
     * <p>
     * Get the <i>non-global</i><code>{@link org.quartz.JobListener}</code>
     * that has the given name.
     * </p>
     */
    public JobListener getJobListener(String name) {
        synchronized (jobListeners) {
            return (JobListener) jobListeners.get(name);
        }
    }

    /**
     * <p>
     * Add the given <code>{@link org.quartz.TriggerListener}</code> to the
     * <code>Scheduler</code>'s<i>global</i> list.
     * </p>
     * 
     * <p>
     * Listeners in the 'global' list receive notification of execution events
     * for ALL <code>{@link org.quartz.Trigger}</code>s.
     * </p>
     */
    public void addGlobalTriggerListener(TriggerListener triggerListener) {
        if (triggerListener.getName() == null || triggerListener.getName().length() == 0) {
            throw new IllegalArgumentException("TriggerListener name cannot be empty.");
        }

        synchronized (globalTriggerListeners) {
            globalTriggerListeners.put(triggerListener.getName(), triggerListener);
        }
    }

    /**
     * <p>
     * Add the given <code>{@link org.quartz.TriggerListener}</code> to the
     * <code>Scheduler</code>'s list, of registered <code>TriggerListener</code>s.
     */
    public void addTriggerListener(TriggerListener triggerListener) {
        if (triggerListener.getName() == null || triggerListener.getName().length() == 0) {
            throw new IllegalArgumentException("TriggerListener name cannot be empty.");
        }

        synchronized (triggerListeners) {
            triggerListeners.put(triggerListener.getName(), triggerListener);
        }
    }

    /**
     * <p>
     * Remove the given <code>{@link org.quartz.TriggerListener}</code> from
     * the <code>Scheduler</code>'s list of <i>global</i> listeners.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     *         
     * @deprecated Use <code>{@link #removeGlobalTriggerListener(String)}</code>
     */
    public boolean removeGlobalTriggerListener(TriggerListener triggerListener) {
        return removeGlobalTriggerListener((triggerListener == null) ? null : triggerListener.getName());
    }

    /**
     * <p>
     * Remove the identifed <code>{@link TriggerListener}</code> from the <code>Scheduler</code>'s
     * list of <i>global</i> listeners.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     */
    public boolean removeGlobalTriggerListener(String name) {
        synchronized (globalTriggerListeners) {
            return (globalTriggerListeners.remove(name) != null);
        }
    }

    /**
     * <p>
     * Remove the identifed <code>{@link org.quartz.TriggerListener}</code>
     * from the <code>Scheduler</code>'s list of registered listeners.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     */
    public boolean removeTriggerListener(String name) {
        synchronized (triggerListeners) {
            return (triggerListeners.remove(name) != null);
        }
    }

    /**
     * <p>
     * Get a list containing all of the <code>{@link org.quartz.TriggerListener}</code>
     * s in the <code>Scheduler</code>'s<i>global</i> list.
     * </p>
     */
    public List getGlobalTriggerListeners() {
        synchronized (globalTriggerListeners) {
            return new LinkedList(globalTriggerListeners.values());
        }
    }

    /**
     * <p>
     * Get a Set containing the names of all the <i>non-global</i><code>{@link org.quartz.TriggerListener}</code>
     * s registered with the <code>Scheduler</code>.
     * </p>
     */
    public Set getTriggerListenerNames() {
        synchronized (triggerListeners) {
            return new HashSet(triggerListeners.keySet());
        }
    }

    /**
     * <p>
     * Get the <i>global</i><code>{@link TriggerListener}</code> that
     * has the given name.
     * </p>
     */
    public TriggerListener getGlobalTriggerListener(String name) {
        synchronized (globalTriggerListeners) {
            return (TriggerListener) globalTriggerListeners.get(name);
        }
    }

    /**
     * <p>
     * Get the <i>non-global</i><code>{@link org.quartz.TriggerListener}</code>
     * that has the given name.
     * </p>
     */
    public TriggerListener getTriggerListener(String name) {
        synchronized (triggerListeners) {
            return (TriggerListener) triggerListeners.get(name);
        }
    }

    /**
     * <p>
     * Register the given <code>{@link SchedulerListener}</code> with the
     * <code>Scheduler</code>.
     * </p>
     */
    public void addSchedulerListener(SchedulerListener schedulerListener) {
        synchronized (schedulerListeners) {
            schedulerListeners.add(schedulerListener);
        }
    }

    /**
     * <p>
     * Remove the given <code>{@link SchedulerListener}</code> from the
     * <code>Scheduler</code>.
     * </p>
     * 
     * @return true if the identifed listener was found in the list, and
     *         removed.
     */
    public boolean removeSchedulerListener(SchedulerListener schedulerListener) {
        synchronized (schedulerListeners) {
            return schedulerListeners.remove(schedulerListener);
        }
    }

    /**
     * <p>
     * Get a List containing all of the <code>{@link SchedulerListener}</code>
     * s registered with the <code>Scheduler</code>.
     * </p>
     */
    public List getSchedulerListeners() {
        synchronized (schedulerListeners) {
            return (List) schedulerListeners.clone();
        }
    }

    protected void notifyJobStoreJobComplete(SchedulingContext ctxt, Trigger trigger, JobDetail detail,
            int instCode) throws JobPersistenceException {

        resources.getJobStore().triggeredJobComplete(ctxt, trigger, detail, instCode);
    }

    protected void notifyJobStoreJobVetoed(SchedulingContext ctxt, Trigger trigger, JobDetail detail, int instCode)
            throws JobPersistenceException {

        resources.getJobStore().triggeredJobComplete(ctxt, trigger, detail, instCode);
    }

    protected void notifySchedulerThread(long candidateNewNextFireTime) {
        if (isSignalOnSchedulingChange()) {
            signaler.signalSchedulingChange(candidateNewNextFireTime);
        }
    }

    private List buildTriggerListenerList(String[] additionalLstnrs) throws SchedulerException {
        List triggerListeners = getGlobalTriggerListeners();
        for (int i = 0; i < additionalLstnrs.length; i++) {
            TriggerListener tl = getTriggerListener(additionalLstnrs[i]);

            if (tl != null) {
                triggerListeners.add(tl);
            } else {
                throw new SchedulerException("TriggerListener '" + additionalLstnrs[i] + "' not found.",
                        SchedulerException.ERR_TRIGGER_LISTENER_NOT_FOUND);
            }
        }

        return triggerListeners;
    }

    private List buildJobListenerList(String[] additionalLstnrs) throws SchedulerException {
        List jobListeners = getGlobalJobListeners();
        for (int i = 0; i < additionalLstnrs.length; i++) {
            JobListener jl = getJobListener(additionalLstnrs[i]);

            if (jl != null) {
                jobListeners.add(jl);
            } else {
                throw new SchedulerException("JobListener '" + additionalLstnrs[i] + "' not found.",
                        SchedulerException.ERR_JOB_LISTENER_NOT_FOUND);
            }
        }

        return jobListeners;
    }

    public boolean notifyTriggerListenersFired(JobExecutionContext jec) throws SchedulerException {
        // build a list of all trigger listeners that are to be notified...
        List triggerListeners = buildTriggerListenerList(jec.getTrigger().getTriggerListenerNames());

        boolean vetoedExecution = false;

        // notify all trigger listeners in the list
        java.util.Iterator itr = triggerListeners.iterator();
        while (itr.hasNext()) {
            TriggerListener tl = (TriggerListener) itr.next();
            try {
                tl.triggerFired(jec.getTrigger(), jec);

                if (tl.vetoJobExecution(jec.getTrigger(), jec)) {
                    vetoedExecution = true;
                }
            } catch (Exception e) {
                SchedulerException se = new SchedulerException(
                        "TriggerListener '" + tl.getName() + "' threw exception: " + e.getMessage(), e);
                se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER);
                throw se;
            }
        }

        return vetoedExecution;
    }

    public void notifyTriggerListenersMisfired(Trigger trigger) throws SchedulerException {
        // build a list of all trigger listeners that are to be notified...
        List triggerListeners = buildTriggerListenerList(trigger.getTriggerListenerNames());

        // notify all trigger listeners in the list
        java.util.Iterator itr = triggerListeners.iterator();
        while (itr.hasNext()) {
            TriggerListener tl = (TriggerListener) itr.next();
            try {
                tl.triggerMisfired(trigger);
            } catch (Exception e) {
                SchedulerException se = new SchedulerException(
                        "TriggerListener '" + tl.getName() + "' threw exception: " + e.getMessage(), e);
                se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER);
                throw se;
            }
        }
    }

    public void notifyTriggerListenersComplete(JobExecutionContext jec, int instCode) throws SchedulerException {
        // build a list of all trigger listeners that are to be notified...
        List triggerListeners = buildTriggerListenerList(jec.getTrigger().getTriggerListenerNames());

        // notify all trigger listeners in the list
        java.util.Iterator itr = triggerListeners.iterator();
        while (itr.hasNext()) {
            TriggerListener tl = (TriggerListener) itr.next();
            try {
                tl.triggerComplete(jec.getTrigger(), jec, instCode);
            } catch (Exception e) {
                SchedulerException se = new SchedulerException(
                        "TriggerListener '" + tl.getName() + "' threw exception: " + e.getMessage(), e);
                se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER);
                throw se;
            }
        }
    }

    public void notifyJobListenersToBeExecuted(JobExecutionContext jec) throws SchedulerException {
        // build a list of all job listeners that are to be notified...
        List jobListeners = buildJobListenerList(jec.getJobDetail().getJobListenerNames());

        // notify all job listeners
        java.util.Iterator itr = jobListeners.iterator();
        while (itr.hasNext()) {
            JobListener jl = (JobListener) itr.next();
            try {
                jl.jobToBeExecuted(jec);
            } catch (Exception e) {
                SchedulerException se = new SchedulerException(
                        "JobListener '" + jl.getName() + "' threw exception: " + e.getMessage(), e);
                se.setErrorCode(SchedulerException.ERR_JOB_LISTENER);
                throw se;
            }
        }
    }

    public void notifyJobListenersWasVetoed(JobExecutionContext jec) throws SchedulerException {
        // build a list of all job listeners that are to be notified...
        List jobListeners = buildJobListenerList(jec.getJobDetail().getJobListenerNames());

        // notify all job listeners
        java.util.Iterator itr = jobListeners.iterator();
        while (itr.hasNext()) {
            JobListener jl = (JobListener) itr.next();
            try {
                jl.jobExecutionVetoed(jec);
            } catch (Exception e) {
                SchedulerException se = new SchedulerException(
                        "JobListener '" + jl.getName() + "' threw exception: " + e.getMessage(), e);
                se.setErrorCode(SchedulerException.ERR_JOB_LISTENER);
                throw se;
            }
        }
    }

    public void notifyJobListenersWasExecuted(JobExecutionContext jec, JobExecutionException je)
            throws SchedulerException {
        // build a list of all job listeners that are to be notified...
        List jobListeners = buildJobListenerList(jec.getJobDetail().getJobListenerNames());

        // notify all job listeners
        java.util.Iterator itr = jobListeners.iterator();
        while (itr.hasNext()) {
            JobListener jl = (JobListener) itr.next();
            try {
                jl.jobWasExecuted(jec, je);
            } catch (Exception e) {
                SchedulerException se = new SchedulerException(
                        "JobListener '" + jl.getName() + "' threw exception: " + e.getMessage(), e);
                se.setErrorCode(SchedulerException.ERR_JOB_LISTENER);
                throw se;
            }
        }
    }

    public void notifySchedulerListenersError(String msg, SchedulerException se) {
        // build a list of all scheduler listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.schedulerError(msg, se);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of error: ", e);
                getLog().error("  Original error (for notification) was: " + msg, se);
            }
        }
    }

    public void notifySchedulerListenersSchduled(Trigger trigger) {
        // build a list of all scheduler listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.jobScheduled(trigger);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of scheduled job." + "  Triger="
                        + trigger.getFullName(), e);
            }
        }
    }

    public void notifySchedulerListenersUnschduled(String triggerName, String triggerGroup) {
        // build a list of all scheduler listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.jobUnscheduled(triggerName, triggerGroup);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of unscheduled job." + "  Triger="
                        + triggerGroup + "." + triggerName, e);
            }
        }
    }

    public void notifySchedulerListenersFinalized(Trigger trigger) {
        // build a list of all scheduler listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.triggerFinalized(trigger);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of finalized trigger." + "  Triger="
                        + trigger.getFullName(), e);
            }
        }
    }

    public void notifySchedulerListenersPausedTrigger(String name, String group) {
        // build a list of all job listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.triggersPaused(name, group);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of paused trigger/group." + "  Triger="
                        + group + "." + name, e);
            }
        }
    }

    public void notifySchedulerListenersResumedTrigger(String name, String group) {
        // build a list of all job listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.triggersResumed(name, group);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of resumed trigger/group." + "  Triger="
                        + group + "." + name, e);
            }
        }
    }

    public void notifySchedulerListenersPausedJob(String name, String group) {
        // build a list of all job listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.jobsPaused(name, group);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of paused job/group." + "  Job=" + group
                        + "." + name, e);
            }
        }
    }

    public void notifySchedulerListenersResumedJob(String name, String group) {
        // build a list of all job listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.jobsResumed(name, group);
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of resumed job/group." + "  Job=" + group
                        + "." + name, e);
            }
        }
    }

    public void notifySchedulerListenersShutdown() {
        // build a list of all job listeners that are to be notified...
        List schedListeners = getSchedulerListeners();

        // notify all scheduler listeners
        java.util.Iterator itr = schedListeners.iterator();
        while (itr.hasNext()) {
            SchedulerListener sl = (SchedulerListener) itr.next();
            try {
                sl.schedulerShutdown();
            } catch (Exception e) {
                getLog().error("Error while notifying SchedulerListener of shutdown.", e);
            }
        }
    }

    public void setJobFactory(JobFactory factory) throws SchedulerException {

        if (factory == null) {
            throw new IllegalArgumentException("JobFactory cannot be set to null!");
        }

        getLog().info("JobFactory set to: " + factory);

        this.jobFactory = factory;
    }

    public JobFactory getJobFactory() {
        return jobFactory;
    }

    /**
     * Interrupt all instances of the identified InterruptableJob executing in 
     * this Scheduler instance.
     *  
     * <p>
     * This method is not cluster aware.  That is, it will only interrupt 
     * instances of the identified InterruptableJob currently executing in this 
     * Scheduler instance, not across the entire cluster.
     * </p>
     * 
     * @see org.quartz.core.RemotableQuartzScheduler#interrupt(org.quartz.core.SchedulingContext, java.lang.String, java.lang.String)
     */
    public boolean interrupt(SchedulingContext ctxt, String jobName, String groupName)
            throws UnableToInterruptJobException {

        if (groupName == null) {
            groupName = Scheduler.DEFAULT_GROUP;
        }

        List jobs = getCurrentlyExecutingJobs();
        java.util.Iterator it = jobs.iterator();

        JobExecutionContext jec = null;
        JobDetail jobDetail = null;
        Job job = null;

        boolean interrupted = false;

        while (it.hasNext()) {
            jec = (JobExecutionContext) it.next();
            jobDetail = jec.getJobDetail();
            if (jobName.equals(jobDetail.getName()) && groupName.equals(jobDetail.getGroup())) {
                job = jec.getJobInstance();
                if (job instanceof InterruptableJob) {
                    ((InterruptableJob) job).interrupt();
                    interrupted = true;
                } else {
                    throw new UnableToInterruptJobException("Job '" + jobName + "' of group '" + groupName
                            + "' can not be interrupted, since it does not implement "
                            + InterruptableJob.class.getName());

                }
            }
        }

        return interrupted;
    }

    private void shutdownPlugins() {
        java.util.Iterator itr = resources.getSchedulerPlugins().iterator();
        while (itr.hasNext()) {
            SchedulerPlugin plugin = (SchedulerPlugin) itr.next();
            plugin.shutdown();
        }
    }

    private void startPlugins() {
        java.util.Iterator itr = resources.getSchedulerPlugins().iterator();
        while (itr.hasNext()) {
            SchedulerPlugin plugin = (SchedulerPlugin) itr.next();
            plugin.start();
        }
    }

}

/////////////////////////////////////////////////////////////////////////////
//
// ErrorLogger - Scheduler Listener Class
//
/////////////////////////////////////////////////////////////////////////////

class ErrorLogger extends SchedulerListenerSupport {
    ErrorLogger() {
    }

    public void schedulerError(String msg, SchedulerException cause) {
        getLog().error(msg, cause);
    }
}

/////////////////////////////////////////////////////////////////////////////
//
// ExecutingJobsManager - Job Listener Class
//
/////////////////////////////////////////////////////////////////////////////

class ExecutingJobsManager implements JobListener {
    HashMap executingJobs = new HashMap();

    int numJobsFired = 0;

    ExecutingJobsManager() {
    }

    public String getName() {
        return getClass().getName();
    }

    public int getNumJobsCurrentlyExecuting() {
        synchronized (executingJobs) {
            return executingJobs.size();
        }
    }

    public void jobToBeExecuted(JobExecutionContext context) {
        numJobsFired++;

        synchronized (executingJobs) {
            executingJobs.put(context.getTrigger().getFireInstanceId(), context);
        }
    }

    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        synchronized (executingJobs) {
            executingJobs.remove(context.getTrigger().getFireInstanceId());
        }
    }

    public int getNumJobsFired() {
        return numJobsFired;
    }

    public List getExecutingJobs() {
        synchronized (executingJobs) {
            return java.util.Collections.unmodifiableList(new ArrayList(executingJobs.values()));
        }
    }

    public void jobExecutionVetoed(JobExecutionContext context) {

    }
}