/*
* 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 Quartz;

using Spring.Objects.Factory;
using Spring.Objects.Factory.Config;
using Spring.Objects.Support;

namespace Spring.Scheduling.Quartz
{
	/// <summary> 
	/// IFactoryObject that exposes a JobDetail object that delegates job execution
	/// to a specified (static or non-static) method. Avoids the need to implement
	/// a one-line Quartz Job that just invokes an existing service method.
	/// </summary>
	/// <remarks>
	/// <p>
	/// Derived from ArgumentConverting MethodInvoker to share common properties and behavior
	/// with MethodInvokingFactoryObject.
	/// </p>
	///  <p>
	/// Supports both concurrently running jobs and non-currently running
	/// ones through the "concurrent" property. Jobs created by this
	/// MethodInvokingJobDetailFactoryObject are by default volatile and durable
	/// (according to Quartz terminology).
	/// </p>
	/// <p><b>NOTE: JobDetails created via this FactoryObject are <i>not</i>
	/// serializable and thus not suitable for persistent job stores.</b>
	/// You need to implement your own Quartz Job as a thin wrapper for each case
	/// where you want a persistent job to delegate to a specific service method.
	/// </p>
    /// </remarks>
	/// <author>Juergen Hoeller</author>
	/// <author>Alef Arendsen</author>
	/// <seealso cref="Concurrent" />
	/// <seealso cref="MethodInvokingFactoryObject" />
	public class MethodInvokingJobDetailFactoryObject : ArgumentConvertingMethodInvoker, 
                                                        IObjectFactoryAware,
                                                        IFactoryObject, 
                                                        IObjectNameAware,
	                                                    IInitializingObject
	{
		private string name;
		private string group;
		private bool concurrent = true;
		private string[] jobListenerNames;
	    private string targetObjectName;
		private string objectName;
		private JobDetail jobDetail;
	    private IObjectFactory objectFactory;

        /// <summary>
        /// Initializes a new instance of the <see cref="MethodInvokingJobDetailFactoryObject"/> class.
        /// </summary>
		public MethodInvokingJobDetailFactoryObject()
		{
            group = SchedulerConstants.DefaultGroup;
		}


		/// <summary> 
		/// Set the name of the job.
		/// Default is the object name of this FactoryObject.
		/// </summary>
		/// <seealso cref="Name" />
		public virtual string Name
		{
			set { name = value; }
		}

		/// <summary> 
		/// Set the group of the job.
		/// Default is the default group of the Scheduler.
		/// </summary>
		/// <seealso cref="Group" />
        /// <seealso cref="SchedulerConstants.DefaultGroup" />
		public virtual string Group
		{
			set { group = value; }
		}

		/// <summary> 
		/// Specify whether or not multiple jobs should be run in a concurrent
		/// fashion. The behavior when one does not want concurrent jobs to be
		/// executed is realized through adding the <see cref="IStatefulJob" /> interface.
		/// More information on stateful versus stateless jobs can be found
		/// <a href="http://www.opensymphony.com/quartz/tutorial.html#jobsMore">here</a>.
		/// <p>
		/// The default setting is to run jobs concurrently.
		/// </p>
		/// </summary>
		public virtual bool Concurrent
		{
			set { concurrent = value; }
		}


        /// <summary>
        /// Gets the job detail.
        /// </summary>
        /// <value>The job detail.</value>
	    protected JobDetail JobDetail
	    {
	        get { return jobDetail; }
	    }

	    /// <summary> 
		/// Set a list of JobListener names for this job, referring to
		/// non-global JobListeners registered with the Scheduler.
		/// </summary>
		/// <remarks>
		/// A JobListener name always refers to the name returned
		/// by the JobListener implementation.
		/// </remarks>
		/// <seealso cref="SchedulerAccessor.JobListeners" />
		/// <seealso cref="IJobListener.Name" />
		public virtual string[] JobListenerNames
		{
			set { jobListenerNames = value; }
		}

        /// <summary>
        /// Set the name of the object in the object factory that created this object.
        /// </summary>
        /// <value>The name of the object in the factory.</value>
        /// <remarks>
        /// Invoked after population of normal object properties but before an init
        /// callback like <see cref="Spring.Objects.Factory.IInitializingObject"/>'s
        /// <see cref="Spring.Objects.Factory.IInitializingObject.AfterPropertiesSet"/>
        /// method or a custom init-method.
        /// </remarks>
		public virtual string ObjectName
		{
			set { objectName = value; }
		}

        /// <summary>
        /// Set the name of the target object in the Spring object factory.
        /// </summary>
	    /// <remarks>
	    /// This is an alternative to specifying TargetObject
	    /// allowing for non-singleton objects to be invoked. Note that specified
	    /// "TargetObject" and "TargetType" values will
	    /// override the corresponding effect of this "TargetObjectName" setting
	    ///(i.e. statically pre-define the object type or even the target object).
        /// </remarks>
	    public string TargetObjectName
	    {
	        set { targetObjectName = value; }
	    }

	    /// <summary>
        /// Sets the object factory.
        /// </summary>
        /// <value>The object factory.</value>
	    public IObjectFactory ObjectFactory
	    {
	        set { objectFactory = value; }
	    }

	    /// <summary>
        /// Return an instance (possibly shared or independent) of the object
        /// managed by this factory.
        /// </summary>
        /// <returns>
        /// An instance (possibly shared or independent) of the object managed by
        /// this factory.
        /// </returns>
        /// <remarks>
        /// <note type="caution">
        /// If this method is being called in the context of an enclosing IoC container and
        /// returns <see langword="null"/>, the IoC container will consider this factory
        /// object as not being fully initialized and throw a corresponding (and most
        /// probably fatal) exception.
        /// </note>
        /// </remarks>
		public virtual object GetObject()
		{
			return jobDetail;
		}

        /// <summary>
        /// Return the <see cref="System.Type"/> of object that this
        /// <see cref="Spring.Objects.Factory.IFactoryObject"/> creates, or
        /// <see langword="null"/> if not known in advance.
        /// </summary>
        /// <value></value>
		public virtual Type ObjectType
		{
			get
			{
                if (targetObjectName != null)
                {
                    if (objectFactory == null)
                    {
                        throw new InvalidOperationException("ObjectFactory must be set when using 'TargetObjectName'");
                    }
                    return objectFactory.GetType(targetObjectName);
                }
			    return typeof (JobDetail);
			}
		}

        /// <summary>
        /// Is the object managed by this factory a singleton or a prototype?
        /// </summary>
        /// <value></value>
		public virtual bool IsSingleton
		{
			get { return true; }
		}


        /// <summary>
        /// Invoked by an <see cref="Spring.Objects.Factory.IObjectFactory"/>
        /// after it has injected all of an object's dependencies.
        /// </summary>
        /// <remarks>
        /// <p>
        /// This method allows the object instance to perform the kind of
        /// initialization only possible when all of it's dependencies have
        /// been injected (set), and to throw an appropriate exception in the
        /// event of misconfiguration.
        /// </p>
        /// <p>
        /// Please do consult the class level documentation for the
        /// <see cref="Spring.Objects.Factory.IObjectFactory"/> interface for a
        /// description of exactly <i>when</i> this method is invoked. In
        /// particular, it is worth noting that the
        /// <see cref="Spring.Objects.Factory.IObjectFactoryAware"/>
        /// and <see cref="Spring.Context.IApplicationContextAware"/>
        /// callbacks will have been invoked <i>prior</i> to this method being
        /// called.
        /// </p>
        /// </remarks>
        /// <exception cref="System.Exception">
        /// In the event of misconfiguration (such as the failure to set a
        /// required property) or if initialization fails.
        /// </exception>
		public virtual void AfterPropertiesSet()
		{
			Prepare();

			// Use specific name if given, else fall back to object name.
			string jobDetailName = (name != null ? name : objectName);

			// Consider the concurrent flag to choose between stateful and stateless job.
			Type jobType = (concurrent ? typeof (MethodInvokingJob) : typeof (StatefulMethodInvokingJob));

			// Build JobDetail instance.
			jobDetail = new JobDetail(jobDetailName, group, jobType);
			jobDetail.JobDataMap.Put("methodInvoker", this);
			jobDetail.Volatile = true;
			jobDetail.Durable = true;

			// Register job listener names.
			if (jobListenerNames != null)
			{
				for (int i = 0; i < jobListenerNames.Length; i++)
				{
					jobDetail.AddJobListener(jobListenerNames[i]);
				}
			}

			PostProcessJobDetail(jobDetail);
		}

		/// <summary>
		/// Callback for post-processing the JobDetail to be exposed by this FactoryObject.
		/// <p>
		/// The default implementation is empty. Can be overridden in subclasses.
		/// </p>
		/// </summary>
		/// <param name="detail">the JobDetail prepared by this FactoryObject</param>
		protected virtual void PostProcessJobDetail(JobDetail detail)
		{
		}

	}
}