#region License

/*
 * Copyright © 2002-2005 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.
 */

#endregion

#region Imports

using System;
using System.Collections;
using Common.Logging;
using Spring.Aop.Framework.DynamicProxy;
using Spring.Core;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Config;
using Spring.Util;

#endregion

namespace Spring.Aop.Framework.AutoProxy
{
    /// <summary>
    /// Abstract IObjectPostProcessor implementation that creates AOP proxies.
    /// This class is completely generic; it contains no special code to handle
    /// any particular aspects, such as pooling aspects.
    /// </summary>
    /// <remarks>
    /// <p>Subclasses must implement the abstract findCandidateAdvisors() method
    /// to return a list of Advisors applying to any object. Subclasses can also
    /// override the inherited shouldSkip() method to exclude certain objects
    /// from autoproxying, but they must be careful to invoke the shouldSkip()
    /// method of this class, which tries to avoid circular reference problems
    /// and infinite loops.</p>
    /// <p>Advisors or advices requiring ordering should implement the Ordered interface.
    /// This class sorts advisors by Ordered order value. Advisors that don't implement
    /// the Ordered interface will be considered to be unordered, and will appear
    /// at the end of the advisor chain in undefined order.</p>
    /// </remarks>
    /// <seealso cref="Spring.Aop.Framework.AutoProxy.AbstractAdvisorAutoProxyCreator.FindCandidateAdvisors"/>
    /// <author>Rod Johnson</author>
    /// <author>Adhari C Mahendra (.NET)</author>
    /// <author>Erich Eichinger</author>
    public abstract class AbstractAdvisorAutoProxyCreator : AbstractAutoProxyCreator
    {
        private readonly ILog Log;
        private IAdvisorRetrievalHelper _advisorRetrievalHelper;

        /// <summary>
        /// Initialize 
        /// </summary>
        protected AbstractAdvisorAutoProxyCreator()
        {
            Log = LogManager.GetLogger(this.GetType());
        }

        /// <summary>
        /// We override this method to ensure that all candidate advisors are materialized
        /// under a stack trace including this object. Otherwise, the dependencies won't
        /// be apparent to the circular-reference prevention strategy in AbstractObjectFactory.
        /// </summary>
        public override IObjectFactory ObjectFactory
        {
            set
            {
                if (!(value is IConfigurableListableObjectFactory))
                {
                    throw new InvalidOperationException("Can not use AdvisorAutoProxyCreator without a ConfigurableListableObjectFactory");
                }
                base.ObjectFactory = value;
                InitObjectFactory((IConfigurableListableObjectFactory)value);
            }
        }

        /// <summary>
        /// An new <see cref="IConfigurableListableObjectFactory"/> was set. Initialize this creator instance
        /// according to the specified object factory.
        /// </summary>
        /// <param name="objectFactory"></param>
        protected virtual void InitObjectFactory(IConfigurableListableObjectFactory objectFactory)
        {
            _advisorRetrievalHelper = CreateAdvisorRetrievalHelper(objectFactory);
        }

        /// <summary>
        /// Create the <see cref="IAdvisorRetrievalHelper"/> for retrieving the list of
        /// applicable advisor objects. The default implementation calls back into
        /// <see cref="IsEligibleAdvisorObject"/> thus it usually is sufficient to just
        /// override <see cref="IsEligibleAdvisorObject"/>. Override <see cref="CreateAdvisorRetrievalHelper"/>
        /// only if you know what you are doing!
        /// </summary>
        /// <param name="objectFactory"></param>
        /// <returns></returns>
        protected virtual IAdvisorRetrievalHelper CreateAdvisorRetrievalHelper(IConfigurableListableObjectFactory objectFactory)
        {
            return new ObjectFactoryAdvisorRetrievalHelperAdapter(this, objectFactory);
        }

        /// <summary>
        /// Return whether the given object is to be proxied, what additional
        /// advices (e.g. AOP Alliance interceptors) and advisors to apply.
        /// </summary>
        /// <remarks>
        /// 	<p>The previous targetName of this method was "GetInterceptorAndAdvisorForObject".
        /// It has been renamed in the course of general terminology clarification
        /// in Spring 1.1. An AOP Alliance Interceptor is just a special form of
        /// Advice, so the generic Advice term is preferred now.</p>
        /// 	<p>The third parameter, customTargetSource, is new in Spring 1.1;
        /// add it to existing implementations of this method.</p>
        /// </remarks>
        /// <param name="targetType">the type of the target object</param>
        /// <param name="targetName">the name of the target object</param>
        /// <param name="customTargetSource">targetSource returned by TargetSource property:
        /// may be ignored. Will be null unless a custom target source is in use.</param>
        /// <returns>
        /// an array of additional interceptors for the particular object;
        /// or an empty array if no additional interceptors but just the common ones;
        /// or null if no proxy at all, not even with the common interceptors.
        /// </returns>
        protected override object[] GetAdvicesAndAdvisorsForObject(Type targetType, string targetName, ITargetSource customTargetSource)
        {
            IList advisors = FindEligibleAdvisors(targetType, targetName);
            if (advisors.Count == 0)
            {
                return DO_NOT_PROXY;
            }
            return (object[]) CollectionUtils.ToArray(advisors, typeof (object));
        }

        /// <summary>
        /// Find all eligible advices and for autoproxying this class.
        /// </summary>
        /// <param name="targetType">the type of the object to be advised</param>
        /// <param name="targetName">the name of the object to be advised</param>
        /// <returns>
        /// the empty list, not null, if there are no pointcuts or interceptors. 
        /// The by-order sorted list of advisors otherwise
        /// </returns>
        protected IList FindEligibleAdvisors(Type targetType, string targetName)
        {
            IList candidateAdvisors = FindCandidateAdvisors(targetType, targetName);
            IList eligibleAdvisors =  FindAdvisorsThatCanApply(candidateAdvisors, targetType, targetName);

            ExtendAdvisors(eligibleAdvisors, targetType, targetName);
            eligibleAdvisors = SortAdvisors(eligibleAdvisors);

            return eligibleAdvisors;
        }

        /// <summary>
        /// Find all possible advisor candidates to use in auto-proxying
        /// </summary>
        /// <param name="targetType">the type of the object to be advised</param>
        /// <param name="targetName">the name of the object to be advised</param>
        /// <returns>the list of candidate advisors</returns>
        protected virtual IList FindCandidateAdvisors(Type targetType, string targetName)
        {
            return _advisorRetrievalHelper.FindAdvisorObjects(targetType, targetName);
        }

        /// <summary>
        /// From the given list of candidate advisors, select the ones that are applicable 
        /// to the given target specified by targetType and name.
        /// </summary>
        /// <param name="candidateAdvisors">the list of candidate advisors to date</param>
        /// <param name="targetType">the target object's type</param>
        /// <param name="targetName">the target object's name</param>
        /// <returns>the list of applicable advisors</returns>
        protected virtual IList FindAdvisorsThatCanApply(IList candidateAdvisors, Type targetType, string targetName)
        {
            if (candidateAdvisors.Count==0)
            {
                return candidateAdvisors;
            }

            ArrayList eligibleAdvisors = new ArrayList();
            foreach(IAdvisor candidate in candidateAdvisors)
            {
                if (candidate is IIntroductionAdvisor && AopUtils.CanApply(candidate, targetType, null))
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info(string.Format("Candidate advisor [{0}] accepted for targetType [{1}]", candidate, targetType));
                    }
                    eligibleAdvisors.Add(candidate);
                }
            }

            bool hasIntroductions = eligibleAdvisors.Count > 0;
            foreach(IAdvisor candidate in candidateAdvisors)
            {
                if (candidate is IIntroductionAdvisor) continue;

                if (AopUtils.CanApply(candidate, targetType, null, hasIntroductions))
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info(string.Format("Candidate advisor [{0}] accepted for targetType [{1}]", candidate, targetType));
                    }
                    eligibleAdvisors.Add(candidate);
                }
                else
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info(string.Format("Candidate advisor [{0}] rejected for targetType [{1}]", candidate, targetType));
                    }
                }
            }
            return eligibleAdvisors;
        }


        /// <summary>
        /// Sorts the advisors.
        /// </summary>
        /// <param name="advisors">The advisors.</param>
        /// <returns></returns>
        protected virtual IList SortAdvisors(IList advisors)
        {
            if (advisors.Count==0)
            {
                return advisors;
            } 

            if (advisors is ArrayList)
                ((ArrayList) advisors).Sort(new OrderComparator());
            else if (advisors is Array)
                Array.Sort((Array) advisors, new OrderComparator());
            return advisors;
        }

        /// <summary>
        /// Extension hook that subclasses can override to register additional advisors,
        /// given the sorted advisors obtained to date.<br/>
        /// The default implementation does nothing.<br/>
        /// Typically used to add advisors that expose contextual information required by some of the later advisors.
        /// </summary>
        /// <param name="advisors">Advisors that have already been identified as applying to a given object</param>
        /// <param name="objectType">the type of the object to be advised</param>
        /// <param name="objectName">the name of the object to be advised</param>
        protected virtual void ExtendAdvisors(IList advisors, Type objectType, string objectName)
        {}

        /// <summary>
        /// Whether the given advisor is eligible for the specified target. The default implementation
        /// always returns true.
        /// </summary>
        /// <param name="advisorName">the advisor name</param>
        /// <param name="targetType">the target object's type</param>
        /// <param name="targetName">the target object's name</param>
        protected virtual bool IsEligibleAdvisorObject(string advisorName, Type targetType, string targetName)
        {
            return true;
        }

        private class ObjectFactoryAdvisorRetrievalHelperAdapter : ObjectFactoryAdvisorRetrievalHelper
        {
            private readonly AbstractAdvisorAutoProxyCreator _owner;

            public ObjectFactoryAdvisorRetrievalHelperAdapter(AbstractAdvisorAutoProxyCreator owner, IConfigurableListableObjectFactory owningFactory) : base(owningFactory)
            {
                _owner = owner;
            }

            protected override bool IsEligibleObject(string advisorName, Type objectType, string objectName)
            {
                return  base.IsEligibleObject(advisorName, objectType, objectName)
                        && _owner.IsEligibleAdvisorObject(advisorName, objectType, objectName);
            }
        }
    }
}