#region License

/*
 * Copyright 2002-2008 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

using System.Collections;
using System.Messaging;
using Common.Logging;
using Spring.Context;
using Spring.Expressions;
using Spring.Messaging.Core;
using Spring.Messaging.Support;
using Spring.Messaging.Support.Converters;
using Spring.Objects.Factory;
using Spring.Reflection.Dynamic;

namespace Spring.Messaging.Listener
{
    /// <summary>
    /// Message listener adapter that delegates the handling of messages to target
    /// listener methods via reflection <see cref="DynamicReflectionManager"/>, 
    /// with flexible message type conversion.
    /// Allows listener methods to operate on message content types, completely
    /// independent from the MSMQ API.
    /// </summary>
    /// <remarks>
    /// <para>
    /// By default, the content of incoming MSMQ messages gets extracted before
    /// being passed into the target handler method, to let the target method
    /// operate on message content types such as String or business object instead of
    /// <see cref="Message"/>. Message type conversion is delegated to a Spring
    /// <see cref="IMessageConverter"/>  By default, an <see cref="XmlMessageConverter"/> 
    /// with TargetType set to System.String is used.  If you do not want such automatic 
    /// message conversion taking place, then be sure to set the
    /// MessageConverter property to null.
    /// </para>
    /// <para>
    /// If a target handler method returns a non-null object (for example, with a
    /// message content type such as <code>String</code>), it will get
    /// wrapped in a MSMQ <code>Message</code> and sent to the response destination
    /// (either using the MSMQ Message.ResponseQueue property or 
    /// <see cref="DefaultResponseQueue"/>) specified default response queue
    /// destination).
    /// </para>
    /// <para>
    /// Find below some examples of method signatures compliant with this adapter class.
    /// This first example uses the default <see cref="XmlMessageConverter"/> that can
    /// marhsall/unmarshall string values from the MSMQ Message.
    /// </para>
    /// <example>
    /// public interface IMyHandler
    /// {
    ///   void HandleMessage(string text);
    /// }
    /// </example>
    /// <para>
    /// The next example indicates a similar method signature but the name of the
    /// handler method name has been changed to "DoWork", using the property
    /// <see cref="DefaultHandlerMethod"/>
    /// </para>
    /// <example>
    /// public interface IMyHandler
    /// {
    ///   void DoWork(string text);
    /// }
    /// </example>
    /// <para>If your <see cref="IMessageConverter"/> implementation will return multiple object
    /// types, overloading the handler method is perfectly acceptible, the most specific matching
    /// method will be used.  A method with an object signature would be consider a 
    /// 'catch-all' method
    /// </para>
    /// <example>
    /// public interface IMyHandler
    /// {
    ///   void DoWork(string text);
    ///   void DoWork(OrderRequest orderRequest);
    ///   void DoWork(InvoiceRequest invoiceRequest);
    ///   void DoWork(object obj);
    /// }
    /// </example>   
    /// <para>
    /// The last example shows how to send a message to the ResponseQueue for those
    /// methods that do not return void.
    /// <example>
    /// public interface MyHandler
    /// {
    ///   string DoWork(string text);
    ///   OrderResponse DoWork(OrderRequest orderRequest);
    ///   InvoiceResponse DoWork(InvoiceRequest invoiceRequest);
    ///   void DoWork(object obj);
    /// }
    /// </example>
    /// </para>
    /// </remarks>
    /// <author>Mark Pollack</author>
    public class MessageListenerAdapter : IMessageListener, IApplicationContextAware, IInitializingObject
    {
        #region Logging

        private static readonly ILog logger = LogManager.GetLogger(typeof (MessageListenerAdapter));

        #endregion

        /// <summary>
        /// Out-of-the-box value for the default listener method: "HandleMessage"
        /// </summary>
        public static readonly string ORIGINAL_DEFAULT_LISTENER_METHOD = "HandleMessage";

        #region Fields

        private IApplicationContext applicationContext;

        private object handlerObject;

        private string defaultHandlerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD;

        private IExpression processingExpression;

        private string defaultResponseQueueName;

        private string messageConverterObjectName;

        private MessageQueueTemplate messageQueueTemplate;

        private IMessageQueueFactory messageQueueFactory;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MessageListenerAdapter"/> class.
        /// </summary>
        public MessageListenerAdapter()
        {
            InitDefaultStrategies();
            handlerObject = this;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MessageListenerAdapter"/> class.
        /// </summary>
        /// <param name="handlerObject">The handler object.</param>
        public MessageListenerAdapter(object handlerObject)
        {
            InitDefaultStrategies();
            this.handlerObject = handlerObject;
        }

        #endregion

        /// <summary>
        /// Gets or sets the handler object to delegate message listening to.
        /// </summary>
        /// <remarks>
        /// Specified listener methods have to be present on this target object.
        /// If no explicit handler object has been specified, listener
        /// methods are expected to present on this adapter instance, that is,
        /// on a custom subclass of this adapter, defining listener methods.
        /// </remarks>
        /// <value>The handler object.</value>
        public object HandlerObject
        {
            get { return handlerObject; }
            set { handlerObject = value; }
        }

        /// <summary>
        /// Gets or sets the default handler method to delegate to,
        /// for the case where no specific listener method has been determined.
        /// Out-of-the-box value is "HandleMessage".
        /// </summary>
        /// <value>The default handler method.</value>
        public string DefaultHandlerMethod
        {
            get { return defaultHandlerMethod; }
            set { 
                defaultHandlerMethod = value;
                processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
            }
        }

        #region IInitializingObject Members

        /// <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 void AfterPropertiesSet()
        {
            if (messageQueueFactory == null)
            {
                DefaultMessageQueueFactory mqf = new DefaultMessageQueueFactory();
                mqf.ApplicationContext = applicationContext;
                messageQueueFactory = mqf;
            }
            if (messageConverterObjectName == null)
            {
                messageConverterObjectName = QueueUtils.RegisterDefaultMessageConverter(applicationContext);
            }
            if (messageQueueTemplate == null)
            {
                messageQueueTemplate = new MessageQueueTemplate();
                messageQueueTemplate.ApplicationContext = ApplicationContext;
                messageQueueTemplate.AfterPropertiesSet();
            }
        }

        #endregion

        #region IApplicationContextAware Members

        /// <summary>
        /// Gets or sets the <see cref="Spring.Context.IApplicationContext"/> that this
        /// object runs in.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Normally this call will be used to initialize the object.
        /// </p>
        /// <p>
        /// Invoked after population of normal object properties but before an
        /// init callback such as
        /// <see cref="Spring.Objects.Factory.IInitializingObject"/>'s
        /// <see cref="Spring.Objects.Factory.IInitializingObject.AfterPropertiesSet"/>
        /// or a custom init-method. Invoked after the setting of any
        /// <see cref="Spring.Context.IResourceLoaderAware"/>'s
        /// <see cref="Spring.Context.IResourceLoaderAware.ResourceLoader"/>
        /// property.
        /// </p>
        /// </remarks>
        /// <exception cref="Spring.Context.ApplicationContextException">
        /// In the case of application context initialization errors.
        /// </exception>
        /// <exception cref="Spring.Objects.ObjectsException">
        /// If thrown by any application context methods.
        /// </exception>
        /// <exception cref="Spring.Objects.Factory.ObjectInitializationException"/>
        public IApplicationContext ApplicationContext
        {
            get { return applicationContext; }
            set { applicationContext = value; }
        }

        #endregion

        /// <summary>
        /// Gets or sets the message queue factory.
        /// </summary>
        /// <value>The message queue factory.</value>
        public IMessageQueueFactory MessageQueueFactory
        {
            get { return messageQueueFactory; }
            set { messageQueueFactory = value; }
        }

        /// <summary>
        /// Sets the name of the default response queue to send response messages to.
        /// This will be applied in case of a request message that does not carry a
        /// "ResponseQueue" value.
        /// <para>Alternatively, specify a response queue via the property
        /// <see cref="DefaultResponseQueue"/>.</para>
        /// </summary>
        /// <value>The name of the default response destination queue.</value>
        public string DefaultResponseQueueName
        {
            get { return defaultResponseQueueName; }
            set { defaultResponseQueueName = value; }
        }

        /// <summary>
        /// Sets the default destination to send response messages to. This will be applied
        /// in case of a request message that does not carry a "ResponseQueue" property
        /// Response destinations are only relevant for listener methods that return
        /// result objects, which will be wrapped in a response message and sent to a
        /// response destination.
        /// <para>
        /// Alternatively, specify a "DefaultResponseQueueName" 
        /// to be dynamically resolved via the MessageQueueFactory.
        /// </para>
        /// </summary>
        /// <value>The default response destination.</value>
        public MessageQueue DefaultResponseQueue
        {
            get
            {
                if (DefaultResponseQueueName != null)
                {
                    return messageQueueFactory.CreateMessageQueue(DefaultResponseQueueName);
                }
                else
                {
                    return null;
                }
            }
        }


        /// <summary>
        /// Gets or sets the name of the message converter object used to resolved a <see cref="IMessageConverter"/>
        /// instance.
        /// </summary>
        /// <value>The name of the message converter object.</value>
        public string MessageConverterObjectName
        {
            get { return messageConverterObjectName; }
            set { messageConverterObjectName = value; }
        }

        /// <summary>
        /// Gets message converter that will convert incoming MSMQ messages to
        /// listener method arguments, and objects returned from listener
        /// methods back to MSMQ messages.
        /// </summary>
        /// <remarks>
        /// <para>The converter used is the one returned by CreateMessageConverter on MessageQueueFactory.
        /// </para>
        /// </remarks>
        /// <value>The message converter.</value>
        public IMessageConverter MessageConverter
        {
            get
            {
                return messageQueueFactory.CreateMessageConverter(MessageConverterObjectName);
            }
        }

        /// <summary>
        /// Sets the message queue template.
        /// </summary>
        /// <remarks>If not set, will create one for it own internal use whne MessageListenerAdapter is constructed.
        /// It maybe useful to share an existing instance if you have an extensively configured MessageQueueTemplate.
        /// </remarks>
        /// <value>The message queue template.</value>
        public MessageQueueTemplate MessageQueueTemplate
        {
            set { messageQueueTemplate = value; }
        }

        #region IMessageListener Members

        /// <summary>
        /// Called when message is received.
        /// </summary>
        /// <param name="message">The message.</param>
        public virtual void OnMessage(Message message)
        {
            object convertedMessage = ExtractMessage(message);

            IDictionary vars = new Hashtable();
            vars["convertedObject"] = convertedMessage;

            //Invoke message handler method and get result.
            object result = processingExpression.GetValue(handlerObject, vars);
            if (result != null)
            {
                HandleResult(result, message);
            }
            else
            {
                logger.Debug("No result object given - no result to handle");
            }
        }

        #endregion

        /// <summary>
        /// Gets the name of the handler method.
        /// </summary>
        /// <param name="originalMessage">The original message.</param>
        /// <param name="extractedMessage">The extracted message.</param>
        /// <returns>The name of the handler method.</returns>
        protected virtual string GetHandlerMethodName(Message originalMessage, object extractedMessage)
        {
            return DefaultHandlerMethod;
        }

        /// <summary>
        /// Initialize the default implementations for the adapter's strategies.
        /// </summary>
        protected virtual void InitDefaultStrategies()
        {
            processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
        }

        /// <summary>
        /// Extracts the message body from the given message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>the content of the message, to be passed into the
        /// listener method as argument</returns>
        protected virtual object ExtractMessage(Message message)
        {
            IMessageConverter converter = MessageConverter;
            if (converter != null)
            {
                return converter.FromMessage(message);
            }
            return message;
        }

        private void HandleResult(object result, Message request)
        {
            if (logger.IsDebugEnabled)
            {
                logger.Debug("Listener method returned result [" + result +
                             "] - generating response message for it");
            }
            Message response = BuildMessage(result);
            PostProcessResponse(request, response);
            MessageQueue destination = GetResponseDestination(request, response);
            SendResponse(destination, response);
        }

        /// <summary>
        /// Sends the given response message to the given destination.
        /// </summary>
        /// <param name="destination">The destination to send to.</param>
        /// <param name="response">The outgoing message about to be sent.</param>
        protected virtual void SendResponse(MessageQueue destination, Message response)
        {
            //Will send with appropriate transaction semantics 
            if (logger.IsDebugEnabled)
            {
                logger.Debug("Sending response message to path = [" + destination.Path + "]");
            }
            messageQueueTemplate.Send(destination, response);
        }

        /// <summary>
        /// Builds a MSMQ message to be sent as response based on the given result object.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns>the MSMQ <code>Message</code> (never <code>null</code>)</returns>
        /// <exception cref="MessagingException">If no messgae converter is specified.</exception>
        protected virtual Message BuildMessage(object result)
        {
            IMessageConverter converter = MessageConverter;
            if (converter != null)
            {
                // This is the default Message converter registered in QueueUtils.RegisterDefaultMessageConverter
                // and used by MessageQueueTemplate and the MessageListenerAdapter if no other Message converage is 
                // set via the property MessageConverteryObjectName.
                if (messageConverterObjectName.Equals("__XmlMessageConverter__"))
                {
                    return converter.ToMessage(result.ToString());
                }
                else
                {
                    return converter.ToMessage(result);
                }
            }
            else
            {
                Message msg = result as Message;
                if (msg == null)
                {
                    throw new MessagingException("No MessageConverter specified - cannot handle message [" + result +
                                                 "]");
                }
                return msg;
            }
        }

        /// <summary>
        /// Post-process the given response message before it will be sent. The default implementation
        /// sets the response's correlation id to the request message's correlation id.
        /// </summary>
        /// <param name="request">The original incoming message.</param>
        /// <param name="response">The outgoing MSMQ message about to be sent.</param>
        protected virtual void PostProcessResponse(Message request, Message response)
        {
            response.CorrelationId = request.CorrelationId;
        }

        /// <summary>
        /// Determine a response destination for the given message.
        /// </summary>
        /// <remarks>
        /// <para>The default implementation first checks the MSMQ ResponseQueue
        ///  of the supplied request; if that is not <code>null</code>
        /// it is returned; if it is <code>null</code>, then the configured
        /// <see cref="DefaultResponseQueue"/> default response destination}
        /// is returned; if this too is <code>null</code>, then an
        /// <see cref="MessagingException"/>is thrown.
        /// </para>
        /// </remarks>
        /// <param name="request">The request.</param>
        /// <param name="response">The response.</param>
        /// <returns></returns>
        protected virtual MessageQueue GetResponseDestination(Message request, Message response)
        {
            MessageQueue replyTo = request.ResponseQueue;
            if (replyTo == null)
            {
                replyTo = DefaultResponseQueue;
                if (replyTo == null)
                {
                    throw new MessagingException("Cannot determine response destination: " +
                                                 "Request message does not contain ResponseQueue destination, and no default response queue set.");
                }
            }
            return replyTo;
        }
    }
}