<commit_before>#region License

/* 
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 
 * 
 * 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.
 * 
 */

#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Security;
using System.Text;
using System.Threading;

using Common.Logging;

using Quartz.Impl;
using Quartz.Impl.Matchers;
using Quartz.Impl.Triggers;
using Quartz.Listener;
using Quartz.Simpl;
using Quartz.Spi;
using Quartz.Util;

namespace Quartz.Core
{
    /// <summary>
    /// This is the heart of Quartz, an indirect implementation of the <see cref="IScheduler" />
    /// interface, containing methods to schedule <see cref="IJob" />s,
    /// register <see cref="IJobListener" /> instances, etc.
    /// </summary>
    /// <seealso cref="IScheduler" />
    /// <seealso cref="QuartzSchedulerThread" />
    /// <seealso cref="IJobStore" />
    /// <seealso cref="IThreadPool" />
    /// <author><NAME></author>
    /// <author><NAME> (.NET)</author>
    public class QuartzScheduler : MarshalByRefObject, IRemotableQuartzScheduler
    {
        private readonly ILog log;
        private static readonly Version version; 

        private readonly QuartzSchedulerResources resources;

        private readonly QuartzSchedulerThread schedThread;
        private readonly SchedulerContext context = new SchedulerContext();

        private readonly IListenerManager listenerManager = new ListenerManagerImpl();

        private readonly IDictionary<string, IJobListener> internalJobListeners = new Dictionary<string, IJobListener>(10);
        private readonly IDictionary<string, ITriggerListener> internalTriggerListeners = new Dictionary<string, ITriggerListener>(10);
        private readonly IList<ISchedulerListener> internalSchedulerListeners = new List<ISchedulerListener>(10);

        private IJobFactory jobFactory = new PropertySettingJobFactory();
        private readonly ExecutingJobsManager jobMgr;
        private readonly ErrorLogger errLogger;
        private readonly ISchedulerSignaler signaler;
        private readonly Random random = new Random();
        private readonly List<object> holdToPreventGc = new List<object>(5);
        private bool signalOnSchedulingChange = true;
        private volatile bool closed;
        private volatile bool shuttingDown;
        private DateTimeOffset? initialStart;
        private bool boundRemotely;
        private readonly TimeSpan dbRetryInterval;

        /// <summary>
        /// Initializes the <see cref="QuartzScheduler"/> class.
        /// </summary>
        static QuartzScheduler()
        {
            var asm = Assembly.GetAssembly(typeof(QuartzScheduler));

            if (asm != null)
            {
                version = asm.GetName().Version;
            }
        }

        /// <summary>
        /// Gets the version of the Quartz Scheduler.
        /// </summary>
        /// <value>The version.</value>
        public string Version
        {
            get { return version.ToString(); }
        } 

        /// <summary>
        /// Gets the version major.
        /// </summary>
        /// <value>The version major.</value>
        public static string VersionMajor
        {
            get { return version.Major.ToString(CultureInfo.InvariantCulture); }
        } 

        /// <summary>
        /// Gets the version minor.
        /// </summary>
        /// <value>The version minor.</value>
        public static string VersionMinor
        {
            get { return version.Minor.ToString(CultureInfo.InvariantCulture); }
        }

        /// <summary>
        /// Gets the version iteration.
        /// </summary>
        /// <value>The version iteration.</value>
        public static string VersionIteration
        {
            get { return version.Build.ToString(CultureInfo.InvariantCulture); }
        }

        /// <summary>
        /// Gets the scheduler signaler.
        /// </summary>
        /// <value>The scheduler signaler.</value>
        public virtual ISchedulerSignaler SchedulerSignaler
        {
            get { return signaler; }
        }

        /// <summary>
        /// Returns the name of the <see cref="QuartzScheduler" />.
        /// </summary>
        public virtual string SchedulerName
        {
            get { return resources.Name; }
        }

        /// <summary> 
        /// Returns the instance Id of the <see cref="QuartzScheduler" />.
        /// </summary>
        public virtual string SchedulerInstanceId
        {
            get { return resources.InstanceId; }
        }


        /// <summary>
        /// Returns the <see cref="SchedulerContext" /> of the <see cref="IScheduler" />.
        /// </summary>
        public virtual SchedulerContext SchedulerContext
        {
            get { return context; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to signal on scheduling change.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if schduler should signal on scheduling change; otherwise, <c>false</c>.
        /// </value>
        public virtual bool SignalOnSchedulingChange
        {
            get { return signalOnSchedulingChange; }
            set { signalOnSchedulingChange = value; }
        }

        /// <summary>
        /// Reports whether the <see cref="IScheduler" /> is paused.
        /// </summary>
        public virtual bool InStandbyMode
        {
            get { return schedThread.Paused; }
        }

        /// <summary>
        /// Gets the job store class.
        /// </summary>
        /// <value>The job store class.</value>
        public virtual Type JobStoreClass
        {
            get { return resources.JobStore.GetType(); }
        }

        /// <summary>
        /// Gets the thread pool class.
        /// </summary>
        /// <value>The thread pool class.</value>
        public virtual Type ThreadPoolClass
        {
            get { return resources.ThreadPool.GetType(); }
        }

        /// <summary>
        /// Gets the size of the thread pool.
        /// </summary>
        /// <value>The size of the thread pool.</value>
        public virtual int ThreadPoolSize
        {
            get { return resources.ThreadPool.PoolSize; }
        }

        /// <summary>
        /// Reports whether the <see cref="IScheduler" /> has been Shutdown.
        /// </summary>
        public virtual bool IsShutdown
        {
            get { return closed; }
        }


        public virtual bool IsShuttingDown
        {
            get { return shuttingDown; }
        }

        public virtual bool IsStarted
        {
            get { return !shuttingDown && !closed && !InStandbyMode && initialStart != null; }
        }

        /// <summary>
        /// Return a list of <see cref="IJobExecutionContext" /> objects that
        /// represent all currently executing Jobs in this Scheduler instance.
        /// <para>
        /// This method is not cluster aware.  That is, it will only return Jobs
        /// currently executing in this Scheduler instance, not across the entire
        /// cluster.
        /// </para>
        /// <para>
        /// 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.
        /// </para>
        /// </summary>
        public virtual IList<IJobExecutionContext> CurrentlyExecutingJobs
        {
            get { return jobMgr.ExecutingJobs; }
        }


        /// <summary>
        /// Register the given <see cref="ISchedulerListener" /> with the
        /// <see cref="IScheduler" />'s list of internal listeners.
        /// </summary>
        /// <param name="schedulerListener"></param>
        public void AddInternalSchedulerListener(ISchedulerListener schedulerListener)
        {
            lock (internalSchedulerListeners)
            {
                internalSchedulerListeners.Add(schedulerListener);
            }
        }

        /// <summary>
        /// Remove the given <see cref="ISchedulerListener" /> from the
        /// <see cref="IScheduler" />'s list of internal listeners.
        /// </summary>
        /// <param name="schedulerListener"></param>
        /// <returns>true if the identified listener was found in the list, andremoved.</returns>
        public bool RemoveInternalSchedulerListener(ISchedulerListener schedulerListener)
        {
            lock (internalSchedulerListeners)
            {
                return internalSchedulerListeners.Remove(schedulerListener);
            }
        }

        /// <summary>
        /// Get a List containing all of the <i>internal</i> <see cref="ISchedulerListener" />s
        /// registered with the <see cref="IScheduler" />.
        /// </summary>
        public IList<ISchedulerListener> InternalSchedulerListeners
        {
            get
            {
                lock (internalSchedulerListeners)
                {
                    return new List<ISchedulerListener>(internalSchedulerListeners).AsReadOnly();
                }
            }
        }

        /// <summary>
        /// Gets or sets the job factory.
        /// </summary>
        /// <value>The job factory.</value>
        public virtual IJobFactory JobFactory
        {
            get { return jobFactory; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentException("JobFactory cannot be set to null!");
                }

                log.Info("JobFactory set to: " + value);

                jobFactory = value;
            }
        }


        /// <summary>
        /// Create a <see cref="QuartzScheduler" /> with the given configuration
        /// properties.
        /// </summary>
        /// <seealso cref="QuartzSchedulerResources" />
        public QuartzScheduler(QuartzSchedulerResources resources, TimeSpan idleWaitTime, TimeSpan dbRetryInterval)
        {
            log = LogManager.GetLogger(GetType());
            this.resources = resources;

            if (resources.JobStore is IJobListener)
            {
                AddInternalJobListener((IJobListener) resources.JobStore);
            }

            schedThread = new QuartzSchedulerThread(this, resources);
            IThreadExecutor schedThreadExecutor = resources.ThreadExecutor;
            schedThreadExecutor.Execute(schedThread);

            if (idleWaitTime > TimeSpan.Zero)
            {
                schedThread.IdleWaitTime = idleWaitTime;
            }
            if (dbRetryInterval > TimeSpan.Zero)
            {
                schedThread.DbFailureRetryInterval = dbRetryInterval;
            }

            jobMgr = new ExecutingJobsManager();
            AddInternalJobListener(jobMgr);
            errLogger = new ErrorLogger();
            AddInternalSchedulerListener(errLogger);

            signaler = new SchedulerSignalerImpl(this, schedThread);

            this.dbRetryInterval = dbRetryInterval;

            log.InfoFormat(CultureInfo.InvariantCulture, "Quartz Scheduler v.{0} created.", Version);
        }

        public TimeSpan DbRetryInterval
        {
            get { return dbRetryInterval; }
        }

        public void Initialize()
        {
            try
            {
                Bind();
            }
            catch (Exception re)
            {
                throw new SchedulerException(
                    "Unable to bind scheduler to remoting.", re);
            }

            log.Info("Scheduler meta-data: " +
                     (new SchedulerMetaData(SchedulerName, SchedulerInstanceId, GetType(), boundRemotely, RunningSince != null,
                                            InStandbyMode, IsShutdown, RunningSince,
                                            NumJobsExecuted, JobStoreClass,
                                            SupportsPersistence, Clustered, ThreadPoolClass,
                                            ThreadPoolSize, Version)));
        }

        /// <summary>
        /// Bind the scheduler to remoting infrastructure.
        /// </summary>
        private void Bind()
        {
            if (resources.SchedulerExporter != null)
            {
                resources.SchedulerExporter.Bind(this);
                boundRemotely = true;
            }
        }

        /// <summary>
        /// Un-bind the scheduler from remoting infrastructure.
        /// </summary>
        private void UnBind()
        {
            if (resources.SchedulerExporter != null)
            {
                resources.SchedulerExporter.UnBind(this);
            }
        }

        /// <summary>
        /// Adds an object that should be kept as reference to prevent
        /// it from being garbage collected.
        /// </summary>
        /// <param name="obj">The obj.</param>
        public virtual void AddNoGCObject(object obj)
        {
            holdToPreventGc.Add(obj);
        }

        /// <summary>
        /// Removes the object from garbae collection protected list.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public virtual bool RemoveNoGCObject(object obj)
        {
            return holdToPreventGc.Remove(obj);
        }

        /// <summary>
        /// Starts the <see cref="QuartzScheduler" />'s threads that fire <see cref="ITrigger" />s.
        /// <para>
        /// All <see cref="ITrigger" />s that have misfired will
        /// be passed to the appropriate TriggerListener(s).
        /// </para>
        /// </summary>
        public virtual void Start()
        {
            if (shuttingDown || closed)
            {
                throw new SchedulerException("The Scheduler cannot be restarted after Shutdown() has been called.");
            }

            NotifySchedulerListenersStarting();

            if (!initialStart.HasValue)
            {
                initialStart = SystemTime.UtcNow();
                resources.JobStore.SchedulerStarted();
                StartPlugins();
            }
            else
            {
                resources.JobStore.SchedulerResumed();
            }

            schedThread.TogglePause(false);

            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} started.", resources.GetUniqueIdentifier()));

            NotifySchedulerListenersStarted();
        }

        public void StartDelayed(TimeSpan delay)
        {
            if (shuttingDown || closed)
            {
                throw new SchedulerException(
                    "The Scheduler cannot be restarted after Shutdown() has been called.");
            }

            DelayedSchedulerStarter starter = new DelayedSchedulerStarter(this, delay, log);
            Thread t = new Thread(starter.Run);
            t.Start();
        }

        /// <summary>
        /// Helper class to start scheduler in a delayed fashion.
        /// </summary>
        private class DelayedSchedulerStarter
        {
            private readonly QuartzScheduler scheduler;
            private readonly TimeSpan delay;
            private readonly ILog logger;

            public DelayedSchedulerStarter(QuartzScheduler scheduler, TimeSpan delay, ILog logger)
            {
                this.scheduler = scheduler;
                this.delay = delay;
                this.logger = logger;
            }

            public void Run()
            {
                try
                {
                    Thread.Sleep(delay);
                }
                catch (ThreadInterruptedException)
                {
                }
                try
                {
                    scheduler.Start();
                }
                catch (SchedulerException se)
                {
                    logger.Error("Unable to start secheduler after startup delay.", se);
                }
            }
        }

        /// <summary>
        /// Temporarily halts the <see cref="QuartzScheduler" />'s firing of <see cref="ITrigger" />s.
        /// <para>
        /// The scheduler is not destroyed, and can be re-started at any time.
        /// </para>
        /// </summary>
        public virtual void Standby()
        {
            resources.JobStore.SchedulerPaused();
            schedThread.TogglePause(true);
            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} paused.", resources.GetUniqueIdentifier()));
            NotifySchedulerListenersInStandbyMode();
        }

        /// <summary>
        /// Gets the running since.
        /// </summary>
        /// <value>The running since.</value>
        public virtual DateTimeOffset? RunningSince
        {
            get { return initialStart; }
        }

        /// <summary>
        /// Gets the number of jobs executed.
        /// </summary>
        /// <value>The number of jobs executed.</value>
        public virtual int NumJobsExecuted
        {
            get { return jobMgr.NumJobsFired; }
        }

        /// <summary>
        /// Gets a value indicating whether this scheduler supports persistence.
        /// </summary>
        /// <value><c>true</c> if supports persistence; otherwise, <c>false</c>.</value>
        public virtual bool SupportsPersistence
        {
            get { return resources.JobStore.SupportsPersistence; }
        }

        public bool Clustered
        {
            get { return resources.JobStore.Clustered; }
        }

        /// <summary>
        /// Halts the <see cref="QuartzScheduler" />'s firing of <see cref="ITrigger" />s,
        /// and cleans up all resources associated with the QuartzScheduler.
        /// Equivalent to <see cref="Shutdown(bool)" />.
        /// <para>
        /// The scheduler cannot be re-started.
        /// </para>
        /// </summary>
        public virtual void Shutdown()
        {
            Shutdown(false);
        }

        /// <summary>
        /// Halts the <see cref="QuartzScheduler" />'s firing of <see cref="ITrigger" />s,
        /// and cleans up all resources associated with the QuartzScheduler.
        /// <para>
        /// The scheduler cannot be re-started.
        /// </para>
        /// </summary>
        /// <param name="waitForJobsToComplete">
        /// if <see langword="true" /> the scheduler will not allow this method
        /// to return until all currently executing jobs have completed.
        /// </param>
        public virtual void Shutdown(bool waitForJobsToComplete)
        {
            if (shuttingDown || closed)
            {
                return;
            }

            shuttingDown = true;

            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} shutting down.", resources.GetUniqueIdentifier()));

            Standby();

            schedThread.Halt();

            NotifySchedulerListenersShuttingdown();

            if ((resources.InterruptJobsOnShutdown && !waitForJobsToComplete) || (resources.InterruptJobsOnShutdownWithWait && waitForJobsToComplete))
            {
                IList<IJobExecutionContext> jobs = CurrentlyExecutingJobs;
                foreach (IJobExecutionContext job in jobs)
                {
                    if (job.JobInstance is IInterruptableJob)
                    {
                        try
                        {
                            ((IInterruptableJob) job.JobInstance).Interrupt();
                        }
                        catch (Exception ex)
                        {
                            // do nothing, this was just a courtesy effort
                            log.WarnFormat("Encountered error when interrupting job {0} during shutdown: {1}", job.JobDetail.Key, ex);
                        }
                    }
                }
            }

            resources.ThreadPool.Shutdown(waitForJobsToComplete);

            if (waitForJobsToComplete)
            {
                while (jobMgr.NumJobsCurrentlyExecuting > 0)
                {
                    try
                    {
                        Thread.Sleep(100);
                    }
                    catch (ThreadInterruptedException)
                    {
                    }
                }
            }

            // 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 (ThreadInterruptedException)
            {
            }

            closed = true;

            if (boundRemotely)
            {
                try
                {
                    UnBind();
                }
                catch (RemotingException)
                {
                }
            }

            ShutdownPlugins();

            resources.JobStore.Shutdown();

            NotifySchedulerListenersShutdown();

            SchedulerRepository.Instance.Remove(resources.Name);

            holdToPreventGc.Clear();

            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} Shutdown complete.", resources.GetUniqueIdentifier()));
        }

        /// <summary>
        /// Validates the state.
        /// </summary>
        public virtual void ValidateState()
        {
            if (IsShutdown)
            {
                throw new SchedulerException("The Scheduler has been Shutdown.");
            }

            // other conditions to check (?)
        }

        /// <summary> 
        /// Add the <see cref="IJob" /> identified by the given
        /// <see cref="IJobDetail" /> to the Scheduler, and
        /// associate the given <see cref="ITrigger" /> with it.
        /// <para>
        /// If the given Trigger does not reference any <see cref="IJob" />, then it
        /// will be set to reference the Job passed with it into this method.
        /// </para>
        /// </summary>
        public virtual DateTimeOffset ScheduleJob(IJobDetail jobDetail, ITrigger trigger)
        {
            ValidateState();

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

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

            if (jobDetail.Key == null)
            {
                throw new SchedulerException("Job's key cannot be null");
            }

            if (jobDetail.JobType == null)
            {
                throw new SchedulerException("Job's class cannot be null");
            }

            IOperableTrigger trig = (IOperableTrigger) trigger;

            if (trigger.JobKey == null)
            {
                trig.JobKey = jobDetail.Key;
            }
            else if (!trigger.JobKey.Equals(jobDetail.Key))
            {
                throw new SchedulerException("Trigger does not reference given job!");
            }

            trig.Validate();

            ICalendar cal = null;
            if (trigger.CalendarName != null)
            {
                cal = resources.JobStore.RetrieveCalendar(trigger.CalendarName);
                if (cal == null)
                {
                    throw new SchedulerException(string.Format(CultureInfo.InvariantCulture, "Calendar not found: {0}", trigger.CalendarName));
                }
            }

            DateTimeOffset? ft = trig.ComputeFirstFireTimeUtc(cal);

            if (!ft.HasValue)
            {
                var message = string.Format("Based on configured schedule, the given trigger '{0}' will never fire.", trigger.Key);
                throw new SchedulerException(message);
            }

            resources.JobStore.StoreJobAndTrigger(jobDetail, trig);
            NotifySchedulerListenersJobAdded(jobDetail);
            NotifySchedulerThread(trigger.GetNextFireTimeUtc());
            NotifySchedulerListenersScheduled(trigger);

            return ft.Value;
        }

        /// <summary>
        /// Schedule the given <see cref="ITrigger" /> with the
        /// <see cref="IJob" /> identified by the <see cref="ITrigger" />'s settings.
        /// </summary>
        public virtual DateTimeOffset ScheduleJob(ITrigger trigger)
        {
            ValidateState();

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

            IOperableTrigger trig = (IOperableTrigger) trigger;
            trig.Validate();

            ICalendar cal = null;
            if (trigger.CalendarName != null)
            {
                cal = resources.JobStore.RetrieveCalendar(trigger.CalendarName);
                if (cal == null)
                {
                    throw new SchedulerException(string.Format(CultureInfo.InvariantCulture, "Calendar not found: {0}", trigger.CalendarName));
                }
            }

            DateTimeOffset? ft = trig.ComputeFirstFireTimeUtc(cal);

            if (!ft.HasValue)
            {
                var message = string.Format("Based on configured schedule, the given trigger '{0}' will never fire.", trigger.Key);
                throw new SchedulerException(message);
            }

            resources.JobStore.StoreTrigger(trig, false);
            NotifySchedulerThread(trigger.GetNextFireTimeUtc());
            NotifySchedulerListenersScheduled(trigger);

            return ft.Value;
        }

        /// <summary>
        /// Add the given <see cref="IJob" /> to the Scheduler - with no associated
        /// <see cref="ITrigger" />. The <see cref="IJob" /> will be 'dormant' until
        /// it is scheduled with a <see cref="ITrigger" />, or <see cref="IScheduler.TriggerJob(Quartz.JobKey)" />
        /// is called for it.
        /// <para>
        /// The <see cref="IJob" /> must by definition be 'durable', if it is not,
        /// SchedulerException will be thrown.
        /// </para>
        /// </summary>
        public virtual void AddJob(IJobDetail jobDetail, bool replace)
        {
            ValidateState();

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

            resources.JobStore.StoreJob(jobDetail, replace);
            NotifySchedulerThread(null);
            NotifySchedulerListenersJobAdded(jobDetail);
        }

        /// <summary>
        /// Delete the identified <see cref="IJob" /> from the Scheduler - and any
        /// associated <see cref="ITrigger" />s.
        /// </summary>
        /// <returns> true if the Job was found and deleted.</returns>
        public virtual bool DeleteJob(JobKey jobKey)
        {
            ValidateState();

            bool result = false;
            IList<ITrigger> triggers = GetTriggersOfJob(jobKey);
            foreach (ITrigger trigger in triggers)
            {<commit_msg>don't consider replace flag when replacing adding job and checking durability<commit_after>#region License

/* 
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 
 * 
 * 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.
 * 
 */

#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Security;
using System.Text;
using System.Threading;

using Common.Logging;

using Quartz.Impl;
using Quartz.Impl.Matchers;
using Quartz.Impl.Triggers;
using Quartz.Listener;
using Quartz.Simpl;
using Quartz.Spi;
using Quartz.Util;

namespace Quartz.Core
{
    /// <summary>
    /// This is the heart of Quartz, an indirect implementation of the <see cref="IScheduler" />
    /// interface, containing methods to schedule <see cref="IJob" />s,
    /// register <see cref="IJobListener" /> instances, etc.
    /// </summary>
    /// <seealso cref="IScheduler" />
    /// <seealso cref="QuartzSchedulerThread" />
    /// <seealso cref="IJobStore" />
    /// <seealso cref="IThreadPool" />
    /// <author><NAME></author>
    /// <author><NAME> (.NET)</author>
    public class QuartzScheduler : MarshalByRefObject, IRemotableQuartzScheduler
    {
        private readonly ILog log;
        private static readonly Version version; 

        private readonly QuartzSchedulerResources resources;

        private readonly QuartzSchedulerThread schedThread;
        private readonly SchedulerContext context = new SchedulerContext();

        private readonly IListenerManager listenerManager = new ListenerManagerImpl();

        private readonly IDictionary<string, IJobListener> internalJobListeners = new Dictionary<string, IJobListener>(10);
        private readonly IDictionary<string, ITriggerListener> internalTriggerListeners = new Dictionary<string, ITriggerListener>(10);
        private readonly IList<ISchedulerListener> internalSchedulerListeners = new List<ISchedulerListener>(10);

        private IJobFactory jobFactory = new PropertySettingJobFactory();
        private readonly ExecutingJobsManager jobMgr;
        private readonly ErrorLogger errLogger;
        private readonly ISchedulerSignaler signaler;
        private readonly Random random = new Random();
        private readonly List<object> holdToPreventGc = new List<object>(5);
        private bool signalOnSchedulingChange = true;
        private volatile bool closed;
        private volatile bool shuttingDown;
        private DateTimeOffset? initialStart;
        private bool boundRemotely;
        private readonly TimeSpan dbRetryInterval;

        /// <summary>
        /// Initializes the <see cref="QuartzScheduler"/> class.
        /// </summary>
        static QuartzScheduler()
        {
            var asm = Assembly.GetAssembly(typeof(QuartzScheduler));

            if (asm != null)
            {
                version = asm.GetName().Version;
            }
        }

        /// <summary>
        /// Gets the version of the Quartz Scheduler.
        /// </summary>
        /// <value>The version.</value>
        public string Version
        {
            get { return version.ToString(); }
        } 

        /// <summary>
        /// Gets the version major.
        /// </summary>
        /// <value>The version major.</value>
        public static string VersionMajor
        {
            get { return version.Major.ToString(CultureInfo.InvariantCulture); }
        } 

        /// <summary>
        /// Gets the version minor.
        /// </summary>
        /// <value>The version minor.</value>
        public static string VersionMinor
        {
            get { return version.Minor.ToString(CultureInfo.InvariantCulture); }
        }

        /// <summary>
        /// Gets the version iteration.
        /// </summary>
        /// <value>The version iteration.</value>
        public static string VersionIteration
        {
            get { return version.Build.ToString(CultureInfo.InvariantCulture); }
        }

        /// <summary>
        /// Gets the scheduler signaler.
        /// </summary>
        /// <value>The scheduler signaler.</value>
        public virtual ISchedulerSignaler SchedulerSignaler
        {
            get { return signaler; }
        }

        /// <summary>
        /// Returns the name of the <see cref="QuartzScheduler" />.
        /// </summary>
        public virtual string SchedulerName
        {
            get { return resources.Name; }
        }

        /// <summary> 
        /// Returns the instance Id of the <see cref="QuartzScheduler" />.
        /// </summary>
        public virtual string SchedulerInstanceId
        {
            get { return resources.InstanceId; }
        }


        /// <summary>
        /// Returns the <see cref="SchedulerContext" /> of the <see cref="IScheduler" />.
        /// </summary>
        public virtual SchedulerContext SchedulerContext
        {
            get { return context; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to signal on scheduling change.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if schduler should signal on scheduling change; otherwise, <c>false</c>.
        /// </value>
        public virtual bool SignalOnSchedulingChange
        {
            get { return signalOnSchedulingChange; }
            set { signalOnSchedulingChange = value; }
        }

        /// <summary>
        /// Reports whether the <see cref="IScheduler" /> is paused.
        /// </summary>
        public virtual bool InStandbyMode
        {
            get { return schedThread.Paused; }
        }

        /// <summary>
        /// Gets the job store class.
        /// </summary>
        /// <value>The job store class.</value>
        public virtual Type JobStoreClass
        {
            get { return resources.JobStore.GetType(); }
        }

        /// <summary>
        /// Gets the thread pool class.
        /// </summary>
        /// <value>The thread pool class.</value>
        public virtual Type ThreadPoolClass
        {
            get { return resources.ThreadPool.GetType(); }
        }

        /// <summary>
        /// Gets the size of the thread pool.
        /// </summary>
        /// <value>The size of the thread pool.</value>
        public virtual int ThreadPoolSize
        {
            get { return resources.ThreadPool.PoolSize; }
        }

        /// <summary>
        /// Reports whether the <see cref="IScheduler" /> has been Shutdown.
        /// </summary>
        public virtual bool IsShutdown
        {
            get { return closed; }
        }


        public virtual bool IsShuttingDown
        {
            get { return shuttingDown; }
        }

        public virtual bool IsStarted
        {
            get { return !shuttingDown && !closed && !InStandbyMode && initialStart != null; }
        }

        /// <summary>
        /// Return a list of <see cref="IJobExecutionContext" /> objects that
        /// represent all currently executing Jobs in this Scheduler instance.
        /// <para>
        /// This method is not cluster aware.  That is, it will only return Jobs
        /// currently executing in this Scheduler instance, not across the entire
        /// cluster.
        /// </para>
        /// <para>
        /// 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.
        /// </para>
        /// </summary>
        public virtual IList<IJobExecutionContext> CurrentlyExecutingJobs
        {
            get { return jobMgr.ExecutingJobs; }
        }


        /// <summary>
        /// Register the given <see cref="ISchedulerListener" /> with the
        /// <see cref="IScheduler" />'s list of internal listeners.
        /// </summary>
        /// <param name="schedulerListener"></param>
        public void AddInternalSchedulerListener(ISchedulerListener schedulerListener)
        {
            lock (internalSchedulerListeners)
            {
                internalSchedulerListeners.Add(schedulerListener);
            }
        }

        /// <summary>
        /// Remove the given <see cref="ISchedulerListener" /> from the
        /// <see cref="IScheduler" />'s list of internal listeners.
        /// </summary>
        /// <param name="schedulerListener"></param>
        /// <returns>true if the identified listener was found in the list, andremoved.</returns>
        public bool RemoveInternalSchedulerListener(ISchedulerListener schedulerListener)
        {
            lock (internalSchedulerListeners)
            {
                return internalSchedulerListeners.Remove(schedulerListener);
            }
        }

        /// <summary>
        /// Get a List containing all of the <i>internal</i> <see cref="ISchedulerListener" />s
        /// registered with the <see cref="IScheduler" />.
        /// </summary>
        public IList<ISchedulerListener> InternalSchedulerListeners
        {
            get
            {
                lock (internalSchedulerListeners)
                {
                    return new List<ISchedulerListener>(internalSchedulerListeners).AsReadOnly();
                }
            }
        }

        /// <summary>
        /// Gets or sets the job factory.
        /// </summary>
        /// <value>The job factory.</value>
        public virtual IJobFactory JobFactory
        {
            get { return jobFactory; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentException("JobFactory cannot be set to null!");
                }

                log.Info("JobFactory set to: " + value);

                jobFactory = value;
            }
        }


        /// <summary>
        /// Create a <see cref="QuartzScheduler" /> with the given configuration
        /// properties.
        /// </summary>
        /// <seealso cref="QuartzSchedulerResources" />
        public QuartzScheduler(QuartzSchedulerResources resources, TimeSpan idleWaitTime, TimeSpan dbRetryInterval)
        {
            log = LogManager.GetLogger(GetType());
            this.resources = resources;

            if (resources.JobStore is IJobListener)
            {
                AddInternalJobListener((IJobListener) resources.JobStore);
            }

            schedThread = new QuartzSchedulerThread(this, resources);
            IThreadExecutor schedThreadExecutor = resources.ThreadExecutor;
            schedThreadExecutor.Execute(schedThread);

            if (idleWaitTime > TimeSpan.Zero)
            {
                schedThread.IdleWaitTime = idleWaitTime;
            }
            if (dbRetryInterval > TimeSpan.Zero)
            {
                schedThread.DbFailureRetryInterval = dbRetryInterval;
            }

            jobMgr = new ExecutingJobsManager();
            AddInternalJobListener(jobMgr);
            errLogger = new ErrorLogger();
            AddInternalSchedulerListener(errLogger);

            signaler = new SchedulerSignalerImpl(this, schedThread);

            this.dbRetryInterval = dbRetryInterval;

            log.InfoFormat(CultureInfo.InvariantCulture, "Quartz Scheduler v.{0} created.", Version);
        }

        public TimeSpan DbRetryInterval
        {
            get { return dbRetryInterval; }
        }

        public void Initialize()
        {
            try
            {
                Bind();
            }
            catch (Exception re)
            {
                throw new SchedulerException(
                    "Unable to bind scheduler to remoting.", re);
            }

            log.Info("Scheduler meta-data: " +
                     (new SchedulerMetaData(SchedulerName, SchedulerInstanceId, GetType(), boundRemotely, RunningSince != null,
                                            InStandbyMode, IsShutdown, RunningSince,
                                            NumJobsExecuted, JobStoreClass,
                                            SupportsPersistence, Clustered, ThreadPoolClass,
                                            ThreadPoolSize, Version)));
        }

        /// <summary>
        /// Bind the scheduler to remoting infrastructure.
        /// </summary>
        private void Bind()
        {
            if (resources.SchedulerExporter != null)
            {
                resources.SchedulerExporter.Bind(this);
                boundRemotely = true;
            }
        }

        /// <summary>
        /// Un-bind the scheduler from remoting infrastructure.
        /// </summary>
        private void UnBind()
        {
            if (resources.SchedulerExporter != null)
            {
                resources.SchedulerExporter.UnBind(this);
            }
        }

        /// <summary>
        /// Adds an object that should be kept as reference to prevent
        /// it from being garbage collected.
        /// </summary>
        /// <param name="obj">The obj.</param>
        public virtual void AddNoGCObject(object obj)
        {
            holdToPreventGc.Add(obj);
        }

        /// <summary>
        /// Removes the object from garbae collection protected list.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public virtual bool RemoveNoGCObject(object obj)
        {
            return holdToPreventGc.Remove(obj);
        }

        /// <summary>
        /// Starts the <see cref="QuartzScheduler" />'s threads that fire <see cref="ITrigger" />s.
        /// <para>
        /// All <see cref="ITrigger" />s that have misfired will
        /// be passed to the appropriate TriggerListener(s).
        /// </para>
        /// </summary>
        public virtual void Start()
        {
            if (shuttingDown || closed)
            {
                throw new SchedulerException("The Scheduler cannot be restarted after Shutdown() has been called.");
            }

            NotifySchedulerListenersStarting();

            if (!initialStart.HasValue)
            {
                initialStart = SystemTime.UtcNow();
                resources.JobStore.SchedulerStarted();
                StartPlugins();
            }
            else
            {
                resources.JobStore.SchedulerResumed();
            }

            schedThread.TogglePause(false);

            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} started.", resources.GetUniqueIdentifier()));

            NotifySchedulerListenersStarted();
        }

        public void StartDelayed(TimeSpan delay)
        {
            if (shuttingDown || closed)
            {
                throw new SchedulerException(
                    "The Scheduler cannot be restarted after Shutdown() has been called.");
            }

            DelayedSchedulerStarter starter = new DelayedSchedulerStarter(this, delay, log);
            Thread t = new Thread(starter.Run);
            t.Start();
        }

        /// <summary>
        /// Helper class to start scheduler in a delayed fashion.
        /// </summary>
        private class DelayedSchedulerStarter
        {
            private readonly QuartzScheduler scheduler;
            private readonly TimeSpan delay;
            private readonly ILog logger;

            public DelayedSchedulerStarter(QuartzScheduler scheduler, TimeSpan delay, ILog logger)
            {
                this.scheduler = scheduler;
                this.delay = delay;
                this.logger = logger;
            }

            public void Run()
            {
                try
                {
                    Thread.Sleep(delay);
                }
                catch (ThreadInterruptedException)
                {
                }
                try
                {
                    scheduler.Start();
                }
                catch (SchedulerException se)
                {
                    logger.Error("Unable to start secheduler after startup delay.", se);
                }
            }
        }

        /// <summary>
        /// Temporarily halts the <see cref="QuartzScheduler" />'s firing of <see cref="ITrigger" />s.
        /// <para>
        /// The scheduler is not destroyed, and can be re-started at any time.
        /// </para>
        /// </summary>
        public virtual void Standby()
        {
            resources.JobStore.SchedulerPaused();
            schedThread.TogglePause(true);
            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} paused.", resources.GetUniqueIdentifier()));
            NotifySchedulerListenersInStandbyMode();
        }

        /// <summary>
        /// Gets the running since.
        /// </summary>
        /// <value>The running since.</value>
        public virtual DateTimeOffset? RunningSince
        {
            get { return initialStart; }
        }

        /// <summary>
        /// Gets the number of jobs executed.
        /// </summary>
        /// <value>The number of jobs executed.</value>
        public virtual int NumJobsExecuted
        {
            get { return jobMgr.NumJobsFired; }
        }

        /// <summary>
        /// Gets a value indicating whether this scheduler supports persistence.
        /// </summary>
        /// <value><c>true</c> if supports persistence; otherwise, <c>false</c>.</value>
        public virtual bool SupportsPersistence
        {
            get { return resources.JobStore.SupportsPersistence; }
        }

        public bool Clustered
        {
            get { return resources.JobStore.Clustered; }
        }

        /// <summary>
        /// Halts the <see cref="QuartzScheduler" />'s firing of <see cref="ITrigger" />s,
        /// and cleans up all resources associated with the QuartzScheduler.
        /// Equivalent to <see cref="Shutdown(bool)" />.
        /// <para>
        /// The scheduler cannot be re-started.
        /// </para>
        /// </summary>
        public virtual void Shutdown()
        {
            Shutdown(false);
        }

        /// <summary>
        /// Halts the <see cref="QuartzScheduler" />'s firing of <see cref="ITrigger" />s,
        /// and cleans up all resources associated with the QuartzScheduler.
        /// <para>
        /// The scheduler cannot be re-started.
        /// </para>
        /// </summary>
        /// <param name="waitForJobsToComplete">
        /// if <see langword="true" /> the scheduler will not allow this method
        /// to return until all currently executing jobs have completed.
        /// </param>
        public virtual void Shutdown(bool waitForJobsToComplete)
        {
            if (shuttingDown || closed)
            {
                return;
            }

            shuttingDown = true;

            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} shutting down.", resources.GetUniqueIdentifier()));

            Standby();

            schedThread.Halt();

            NotifySchedulerListenersShuttingdown();

            if ((resources.InterruptJobsOnShutdown && !waitForJobsToComplete) || (resources.InterruptJobsOnShutdownWithWait && waitForJobsToComplete))
            {
                IList<IJobExecutionContext> jobs = CurrentlyExecutingJobs;
                foreach (IJobExecutionContext job in jobs)
                {
                    if (job.JobInstance is IInterruptableJob)
                    {
                        try
                        {
                            ((IInterruptableJob) job.JobInstance).Interrupt();
                        }
                        catch (Exception ex)
                        {
                            // do nothing, this was just a courtesy effort
                            log.WarnFormat("Encountered error when interrupting job {0} during shutdown: {1}", job.JobDetail.Key, ex);
                        }
                    }
                }
            }

            resources.ThreadPool.Shutdown(waitForJobsToComplete);

            if (waitForJobsToComplete)
            {
                while (jobMgr.NumJobsCurrentlyExecuting > 0)
                {
                    try
                    {
                        Thread.Sleep(100);
                    }
                    catch (ThreadInterruptedException)
                    {
                    }
                }
            }

            // 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 (ThreadInterruptedException)
            {
            }

            closed = true;

            if (boundRemotely)
            {
                try
                {
                    UnBind();
                }
                catch (RemotingException)
                {
                }
            }

            ShutdownPlugins();

            resources.JobStore.Shutdown();

            NotifySchedulerListenersShutdown();

            SchedulerRepository.Instance.Remove(resources.Name);

            holdToPreventGc.Clear();

            log.Info(string.Format(CultureInfo.InvariantCulture, "Scheduler {0} Shutdown complete.", resources.GetUniqueIdentifier()));
        }

        /// <summary>
        /// Validates the state.
        /// </summary>
        public virtual void ValidateState()
        {
            if (IsShutdown)
            {
                throw new SchedulerException("The Scheduler has been Shutdown.");
            }

            // other conditions to check (?)
        }

        /// <summary> 
        /// Add the <see cref="IJob" /> identified by the given
        /// <see cref="IJobDetail" /> to the Scheduler, and
        /// associate the given <see cref="ITrigger" /> with it.
        /// <para>
        /// If the given Trigger does not reference any <see cref="IJob" />, then it
        /// will be set to reference the Job passed with it into this method.
        /// </para>
        /// </summary>
        public virtual DateTimeOffset ScheduleJob(IJobDetail jobDetail, ITrigger trigger)
        {
            ValidateState();

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

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

            if (jobDetail.Key == null)
            {
                throw new SchedulerException("Job's key cannot be null");
            }

            if (jobDetail.JobType == null)
            {
                throw new SchedulerException("Job's class cannot be null");
            }

            IOperableTrigger trig = (IOperableTrigger) trigger;

            if (trigger.JobKey == null)
            {
                trig.JobKey = jobDetail.Key;
            }
            else if (!trigger.JobKey.Equals(jobDetail.Key))
            {
                throw new SchedulerException("Trigger does not reference given job!");
            }

            trig.Validate();

            ICalendar cal = null;
            if (trigger.CalendarName != null)
            {
                cal = resources.JobStore.RetrieveCalendar(trigger.CalendarName);
                if (cal == null)
                {
                    throw new SchedulerException(string.Format(CultureInfo.InvariantCulture, "Calendar not found: {0}", trigger.CalendarName));
                }
            }

            DateTimeOffset? ft = trig.ComputeFirstFireTimeUtc(cal);

            if (!ft.HasValue)
            {
                var message = string.Format("Based on configured schedule, the given trigger '{0}' will never fire.", trigger.Key);
                throw new SchedulerException(message);
            }

            resources.JobStore.StoreJobAndTrigger(jobDetail, trig);
            NotifySchedulerListenersJobAdded(jobDetail);
            NotifySchedulerThread(trigger.GetNextFireTimeUtc());
            NotifySchedulerListenersScheduled(trigger);

            return ft.Value;
        }

        /// <summary>
        /// Schedule the given <see cref="ITrigger" /> with the
        /// <see cref="IJob" /> identified by the <see cref="ITrigger" />'s settings.
        /// </summary>
        public virtual DateTimeOffset ScheduleJob(ITrigger trigger)
        {
            ValidateState();

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

            IOperableTrigger trig = (IOperableTrigger) trigger;
            trig.Validate();

            ICalendar cal = null;
            if (trigger.CalendarName != null)
            {
                cal = resources.JobStore.RetrieveCalendar(trigger.CalendarName);
                if (cal == null)
                {
                    throw new SchedulerException(string.Format(CultureInfo.InvariantCulture, "Calendar not found: {0}", trigger.CalendarName));
                }
            }

            DateTimeOffset? ft = trig.ComputeFirstFireTimeUtc(cal);

            if (!ft.HasValue)
            {
                var message = string.Format("Based on configured schedule, the given trigger '{0}' will never fire.", trigger.Key);
                throw new SchedulerException(message);
            }

            resources.JobStore.StoreTrigger(trig, false);
            NotifySchedulerThread(trigger.GetNextFireTimeUtc());
            NotifySchedulerListenersScheduled(trigger);

            return ft.Value;
        }

        /// <summary>
        /// Add the given <see cref="IJob" /> to the Scheduler - with no associated
        /// <see cref="ITrigger" />. The <see cref="IJob" /> will be 'dormant' until
        /// it is scheduled with a <see cref="ITrigger" />, or <see cref="IScheduler.TriggerJob(Quartz.JobKey)" />
        /// is called for it.
        /// <para>
        /// The <see cref="IJob" /> must by definition be 'durable', if it is not,
        /// SchedulerException will be thrown.
        /// </para>
        /// </summary>
        public virtual void AddJob(IJobDetail jobDetail, bool replace)
        {
            ValidateState();

            if (!jobDetail.Durable)
            {
                throw new SchedulerException("Jobs added with no trigger must be durable.");
            }

            resources.JobStore.StoreJob(jobDetail, replace);
            NotifySchedulerThread(null);
            NotifySchedulerListenersJobAdded(jobDetail);
        }

        /// <summary>
        /// Delete the identified <see cref="IJob" /> from the Scheduler - and any
        /// associated <see cref="ITrigger" />s.
        /// </summary>
        /// <returns> true if the Job was found and deleted.</returns>
        public virtual bool DeleteJob(JobKey jobKey)
        {
            ValidateState();

            bool result = false;
            IList<ITrigger> triggers = GetTriggersOfJob(jobKey);
            foreach (ITrigger trigger in triggers)
            {
{'fraction_non_alphanumeric': 0.09964783671122611, 'fraction_numerical': 0.0009343107661348283, 'mean_word_length': 1.546415336047948, 'pattern_counts': {'":': 0, '<': 431, '<?xml version=': 0, '>': 437, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5267348', 'n_tokens_mistral': 13761, 'n_tokens_neox': 13244, 'n_words': 3695}