/*
* Copyright 2002-2006 the original author or authors.
*
* 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.
*/
using System;
using System.Threading;

using Quartz;
using Quartz.Spi;
using Quartz.Util;

namespace Spring.Scheduling.Quartz
{
	/// <summary> 
	/// JobFactory implementation that supports <see cref="ThreadStart" />
	/// objects as well as standard Quartz <see cref="IJob" /> instances.
	/// </summary>
	/// <author>Juergen Hoeller</author>
	/// <author>Marko Lahma (.NET)</author>
	/// <seealso cref="DelegatingJob" />
	/// <seealso cref="AdaptJob(object)" />
	public class AdaptableJobFactory : IJobFactory
	{
        /// <summary>
        /// Called by the scheduler at the time of the trigger firing, in order to
        /// produce a <see cref="IJob"/> instance on which to call Execute.
        /// </summary>
        /// <remarks>
        /// It should be extremely rare for this method to throw an exception -
        /// basically only the the case where there is no way at all to instantiate
        /// and prepare the Job for execution.  When the exception is thrown, the
        /// Scheduler will move all triggers associated with the Job into the
        /// <see cref="TriggerState.Error"/> state, which will require human
        /// intervention (e.g. an application restart after fixing whatever
        /// configuration problem led to the issue wih instantiating the Job.
        /// </remarks>
        /// <param name="bundle">The TriggerFiredBundle from which the <see cref="JobDetail"/>
        /// and other info relating to the trigger firing can be obtained.</param>
        /// <returns>the newly instantiated Job</returns>
        /// <throws>SchedulerException if there is a problem instantiating the Job.</throws>
		public virtual IJob NewJob(TriggerFiredBundle bundle)
		{
			try
			{
				object jobObject = CreateJobInstance(bundle);
				return AdaptJob(jobObject);
			}
			catch (Exception ex)
			{
				throw new SchedulerException("Job instantiation failed", ex);
			}
		}

		/// <summary> 
		/// Create an instance of the specified job class.
		/// <p>
		/// Can be overridden to post-process the job instance.
		/// </p>
		/// </summary>
		/// <param name="bundle">
		/// The TriggerFiredBundle from which the JobDetail
		/// and other info relating to the trigger firing can be obtained.
		/// </param>
		/// <returns>The job instance.</returns>
		protected virtual object CreateJobInstance(TriggerFiredBundle bundle)
		{
			return ObjectUtils.InstantiateType(bundle.JobDetail.JobType);
		}

		/// <summary> 
		/// Adapt the given job object to the Quartz Job interface.
		/// </summary>
		/// <remarks>
		/// The default implementation supports straight Quartz Jobs
		/// as well as Runnables, which get wrapped in a DelegatingJob.
        /// </remarks>
		/// <param name="jobObject">
		/// The original instance of the specified job class.
		/// </param>
		/// <returns>The adapted Quartz Job instance.</returns>
		/// <seealso cref="DelegatingJob" />
		protected virtual IJob AdaptJob(object jobObject)
		{
			if (jobObject is IJob)
			{
				return (IJob) jobObject;
			}
            else if (jobObject is ThreadStart)
			{
                return new DelegatingJob((ThreadStart)jobObject);
			}
            else if (jobObject is IThreadRunnable)
            {
                return new DelegatingJob(new ThreadStart(((IThreadRunnable) jobObject).Run));
            }
			else
			{
				throw new ArgumentException(
                    string.Format("Unable to execute job class [{0}]: only [IJob] and [ThreadStart] supported.",
					              jobObject.GetType().FullName));
			}
		}
	}
}