#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;
using System.Collections;
using System.Messaging;
using Common.Logging;
using Spring.Context;
using Spring.Messaging.Support;
using Spring.Messaging.Support.Converters;
using Spring.Threading;
using Spring.Util;

namespace Spring.Messaging.Core
{
    /// <summary>
    /// A <see cref="IMessageQueueFactory"/> implementation that caches MessageQueue and IMessageConverter
    /// instances.  The MessageQueue objects are created by retrieving them by-name from the 
    /// ApplicationContext. 
    /// </summary>
    /// <author>Mark Pollack</author>
    public class DefaultMessageQueueFactory : IMessageQueueFactory, IApplicationContextAware
    {
        /// <summary>
        /// The <see cref="Common.Logging.ILog"/> instance for this class.
        /// </summary>
        private readonly ILog log = LogManager.GetLogger(typeof(DefaultMessageQueueFactory));


        private static readonly string QUEUE_DICTIONARY_SLOTNAME =
            UniqueKey.GetTypeScopedString(typeof (DefaultMessageQueueFactory), "Queue");

        private static readonly string CONVERTER_DICTIONARY_SLOTNAME =
            UniqueKey.GetTypeScopedString(typeof (DefaultMessageQueueFactory), "Converter");

        private IConfigurableApplicationContext applicationContext;

        #region IMessageQueueFactory Members

        /// <summary>
        /// Registers the message queue, its creation specified via the factory method
        /// MessageQueueCreatorDelegate, with the provided name in the application context
        /// </summary>
        /// <param name="messageQueueObjectName">Name of the message queue object.</param>
        /// <param name="messageQueueCreatorDelegate">The message queue creator delegate.</param>
        public void RegisterMessageQueue(string messageQueueObjectName,
                                 MessageQueueCreatorDelegate messageQueueCreatorDelegate)
        {
            MessageQueueFactoryObject mqfo = new MessageQueueFactoryObject();
            mqfo.MessageCreatorDelegate = messageQueueCreatorDelegate;
            applicationContext.ObjectFactory.RegisterSingleton(messageQueueObjectName, mqfo);
            IDictionary caches = applicationContext.GetObjectsOfType(typeof(MessageQueueMetadataCache));
            foreach (DictionaryEntry entry in caches)
            {
                ((MessageQueueMetadataCache) entry.Value).Insert(mqfo.Path, new MessageQueueMetadata(mqfo.RemoteQueue, mqfo.RemoteQueueIsTransactional));
            }             
        }

        /// <summary>
        /// Creates the message queue given its name in the application context.
        /// </summary>
        /// <param name="messageQueueObjectName">Name of the message queue object.</param>
        /// <returns>
        /// A MessageQueue instance configured via the application context
        /// </returns>
        public MessageQueue CreateMessageQueue(string messageQueueObjectName)
        {
            AssertUtils.ArgumentHasText(messageQueueObjectName, "DefaultMessageQueueObjectName");
            IDictionary queues = LogicalThreadContext.GetData(QUEUE_DICTIONARY_SLOTNAME) as IDictionary;
            if (queues == null)
            {
                queues = new Hashtable();
                LogicalThreadContext.SetData(QUEUE_DICTIONARY_SLOTNAME, queues);
            }
            if (!queues.Contains(messageQueueObjectName))
            {
                MessageQueue mq = applicationContext.GetObject(messageQueueObjectName) as MessageQueue;
                queues.Add(messageQueueObjectName, mq);
            }
            return queues[messageQueueObjectName] as MessageQueue;
        }

        /// <summary>
        /// Determines whether the application context contains the message queue object definition.
        /// </summary>
        /// <param name="messageQueueObjectName">Name of the message queue object.</param>
        /// <returns>
        /// 	<c>true</c> if the application context contains the specified message queue object name; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsMessageQueue(string messageQueueObjectName)
        {
            return applicationContext.ContainsObject(messageQueueObjectName);
        }

        /// <summary>
        /// Registers the message converter.
        /// </summary>
        /// <param name="messageConverterName">Name of the message converter.</param>
        /// <param name="messageConverterCreatorDelegate">The message converter creator delegate.</param>
        public void RegisterMessageConverter(string messageConverterName,
                                             MessageConverterCreatorDelegate messageConverterCreatorDelegate)
        {
            MessageConverterFactoryObject mcfo = new MessageConverterFactoryObject();
            mcfo.MessageConverterCreatorDelegate = messageConverterCreatorDelegate;
            applicationContext.ObjectFactory.RegisterSingleton(messageConverterName, mcfo);
        }

        /// <summary>
        /// Creates the message converter given its name in the application context.
        /// </summary>
        /// <param name="messageConverterObjectName">Name of the message converter object.</param>
        /// <returns>
        /// A IMessageConverter instance configured via the application context
        /// </returns>
        public IMessageConverter CreateMessageConverter(string messageConverterObjectName)
        {
            AssertUtils.ArgumentHasText(messageConverterObjectName, "MessgaeFormatterObjectName");
            IDictionary converters = LogicalThreadContext.GetData(CONVERTER_DICTIONARY_SLOTNAME) as IDictionary;
            if (converters == null)
            {
                converters = new Hashtable();
                LogicalThreadContext.SetData(CONVERTER_DICTIONARY_SLOTNAME, converters);
            }
            if (!converters.Contains(messageConverterObjectName))
            {
                IMessageConverter mc =
                    (IMessageConverter)
                    applicationContext.GetObject(messageConverterObjectName, typeof (IMessageConverter));
                if (applicationContext.ObjectFactory.GetObjectDefinition(messageConverterObjectName).IsSingleton)
                {
                    log.Warn("MessageConverter with name = [" + messageConverterObjectName + "] should be declared with singleton=false.  Using Clone() to create independent instance for thread local storage");
                    converters.Add(messageConverterObjectName, mc.Clone());
                }
                else
                {                              
                    converters.Add(messageConverterObjectName, mc);
                }
            }
            return converters[messageConverterObjectName] as IMessageConverter;
        }


        /// <summary>
        /// Determines whether the application context contains the message queue object definition.
        /// </summary>
        /// <param name="messageConverterObjectName">Name of the message converter object.</param>
        /// <returns>
        /// 	<c>true</c> if the application context contains the specified message message converter object name; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsMessageConverter(string messageConverterObjectName)
        {
            return applicationContext.ContainsObject(messageConverterObjectName);
        }

        #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
            {
                AssertUtils.ArgumentNotNull(value, "An ApplicationContext instance is required");
                IConfigurableApplicationContext ctx = value as IConfigurableApplicationContext;
                if (ctx == null)
                {
                    throw new InvalidOperationException(
                        "Implementations of IApplicationContext must also implement IConfigurableApplicationContext");
                }

                applicationContext = ctx;
            }
        }

        #endregion
    }
}