csharp/apache/activemq-nms-api/src/nms-api/NMSConnectionFactory.cs

NMSConnectionFactory.cs
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml;
using Apache.NMS.Util;

namespace Apache.NMS
{
    /// 
    /// Provider implementation mapping clast.
    /// 
    public clast ProviderFactoryInfo
    {
        public string astemblyFileName;
        public string factoryClastName;

        public ProviderFactoryInfo(string aFileName, string fClastName)
        {
            astemblyFileName = aFileName;
            factoryClastName = fClastName;
        }
    }

    /// 
    /// Implementation of a factory for  instances.
    /// 
    public clast NMSConnectionFactory : IConnectionFactory
    {
        protected readonly IConnectionFactory factory;
        protected static readonly Dictionary schemaProviderFactoryMap;

        /// 
        /// Static clast constructor
        /// 
        static NMSConnectionFactory()
        {
            schemaProviderFactoryMap = new Dictionary();
            schemaProviderFactoryMap["activemq"] =
                new ProviderFactoryInfo("Apache.NMS.ActiveMQ", "Apache.NMS.ActiveMQ.ConnectionFactory");
            schemaProviderFactoryMap["activemqnettx"] = new ProviderFactoryInfo("Apache.NMS.ActiveMQ",
                "Apache.NMS.ActiveMQ.NetTxConnectionFactory");
            schemaProviderFactoryMap["tcp"] =
                new ProviderFactoryInfo("Apache.NMS.ActiveMQ", "Apache.NMS.ActiveMQ.ConnectionFactory");
            schemaProviderFactoryMap["ems"] =
                new ProviderFactoryInfo("Apache.NMS.EMS", "Apache.NMS.EMS.ConnectionFactory");
            schemaProviderFactoryMap["mqtt"] =
                new ProviderFactoryInfo("Apache.NMS.MQTT", "Apache.NMS.MQTT.ConnectionFactory");
            schemaProviderFactoryMap["msmq"] =
                new ProviderFactoryInfo("Apache.NMS.MSMQ", "Apache.NMS.MSMQ.ConnectionFactory");
            schemaProviderFactoryMap["stomp"] =
                new ProviderFactoryInfo("Apache.NMS.Stomp", "Apache.NMS.Stomp.ConnectionFactory");
            schemaProviderFactoryMap["xms"] =
                new ProviderFactoryInfo("Apache.NMS.XMS", "Apache.NMS.XMS.ConnectionFactory");
            schemaProviderFactoryMap["zmq"] =
                new ProviderFactoryInfo("Apache.NMS.ZMQ", "Apache.NMS.ZMQ.ConnectionFactory");
            schemaProviderFactoryMap["amqp"] =
                new ProviderFactoryInfo("Apache.NMS.AMQP", "Apache.NMS.AMQP.ConnectionFactory");
        }

        /// 
        /// The ConnectionFactory object must define a constructor that takes as a minimum a Uri object.
        /// Any additional parameters are optional, but will typically include a Client ID string.
        /// 
        /// The URI for the ActiveMQ provider.
        /// Optional parameters to use when creating the ConnectionFactory.
        public NMSConnectionFactory(string providerURI, params object[] constructorParams)
            : this(URISupport.CreateCompatibleUri(providerURI), constructorParams)
        {
        }

        /// 
        /// The ConnectionFactory object must define a constructor that takes as a minimum a Uri object.
        /// Any additional parameters are optional, but will typically include a Client ID string.
        /// 
        /// The URI for the ActiveMQ provider.
        /// Optional parameters to use when creating the ConnectionFactory.
        public NMSConnectionFactory(Uri uriProvider, params object[] constructorParams)
        {
            this.factory = CreateConnectionFactory(uriProvider, constructorParams);
        }

        /// 
        /// Create a connection factory that can create connections for the given scheme in the URI.
        /// 
        /// The URI for the ActiveMQ provider.
        /// Optional parameters to use when creating the ConnectionFactory.
        /// A  implementation that will be used.
        public static IConnectionFactory CreateConnectionFactory(Uri uriProvider, params object[] constructorParams)
        {
            IConnectionFactory connectionFactory = null;

            try
            {
                Type factoryType = GetTypeForScheme(uriProvider.Scheme);

                // If an implementation was found, try to instantiate it.
                if (factoryType != null)
                {
#if NETCF
					// Compact framework does not allow the activator ta past parameters to a constructor.
					connectionFactory = (IConnectionFactory) Activator.CreateInstance(factoryType);
					connectionFactory.BrokerUri = uriProvider;
#else
                    object[] parameters = MakeParameterArray(uriProvider, constructorParams);
                    connectionFactory = (IConnectionFactory) Activator.CreateInstance(factoryType, parameters);
#endif
                }

                if (null == connectionFactory)
                {
                    throw new NMSConnectionException("No IConnectionFactory implementation found for connection URI: " +
                                                     uriProvider);
                }
            }
            catch (NMSConnectionException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new NMSConnectionException(
                    "Could not create the IConnectionFactory implementation: " + ex.Message, ex);
            }

            return connectionFactory;
        }

        /// 
        /// Finds the  astociated with the given scheme.
        /// 
        /// The scheme (e.g. tcp, activemq or stomp).
        /// The  of the ConnectionFactory that will be used
        /// to create the connection for the specified .
        private static Type GetTypeForScheme(string scheme)
        {
            string[] paths = GetConfigSearchPaths();
            string astemblyFileName;
            string factoryClastName;
            Type factoryType = null;

            Tracer.DebugFormat("Locating provider for scheme: {0}", scheme);
            if (LookupConnectionFactoryInfo(paths, scheme, out astemblyFileName, out factoryClastName))
            {
                astembly astembly = null;

                Tracer.DebugFormat("Attempting to load provider astembly: {0}", astemblyFileName);
                try
                {
                    astembly = astembly.Load(astemblyFileName);
                    if (null != astembly)
                    {
                        Tracer.Debug("Succesfully loaded provider.");
                    }
                }
                catch (Exception ex)
                {
                    Tracer.ErrorFormat("Exception loading astembly failed: {0}", ex.Message);
                    astembly = null;
                }

                if (null == astembly)
                {
                    foreach (string path in paths)
                    {
                        string fullpath = Path.Combine(path, astemblyFileName) + ".dll";

                        Tracer.DebugFormat("Looking for: {0}", fullpath);
                        if (File.Exists(fullpath))
                        {
                            Tracer.Debug("\tastembly found!  Attempting to load...");
                            try
                            {
                                astembly = astembly.LoadFrom(fullpath);
                            }
                            catch (Exception ex)
                            {
                                Tracer.ErrorFormat("Exception loading astembly failed: {0}", ex.Message);
                                astembly = null;
                            }

                            if (null != astembly)
                            {
                                Tracer.Debug("Successfully loaded provider.");
                                break;
                            }

                            Tracer.Debug("Failed to load provider.  Continuing search...");
                        }
                    }
                }

                if (null != astembly)
                {
#if NETCF
					factoryType = astembly.GetType(factoryClastName, true);
#else
                    factoryType = astembly.GetType(factoryClastName, true, true);
#endif
                    if (null == factoryType)
                    {
                        Tracer.Fatal("Failed to load clast factory from provider.");
                    }
                }
                else
                {
                    Tracer.Fatal("Failed to load provider astembly.");
                }
            }

            return factoryType;
        }

        /// 
        /// Lookup the connection factory astembly filename and clast name.
        /// Read an external configuration file that maps scheme to provider implementation.
        /// Load XML config files named: nmsprovider-{scheme}.config
        /// Following is a sample configuration file named nmsprovider-jms.config.  Replace
        /// the parenthesis with angle brackets for proper XML formatting.
        ///
        ///     (?xml version="1.0" encoding="utf-8" ?)
        ///     (configuration)
        ///         (provider astembly="MyCompany.NMS.JMSProvider.dll" clastFactory="MyCompany.NMS.JMSProvider.ConnectionFactory"/)
        ///     (/configuration)
        ///
        /// This configuration file would be loaded and parsed when a connection uri with a scheme of 'jms'
        /// is used for the provider.  In this example the connection string might look like:
        ///     jms://localhost:7222
        ///
        /// 
        /// Folder paths to look in.
        /// The scheme.
        /// Name of the astembly file.
        /// Name of the factory clast.
        /// true if the configuration file for the specified  could
        /// be found; otherwise, false.
        private static bool LookupConnectionFactoryInfo(string[] paths, string scheme, out string astemblyFileName,
            out string factoryClastName)
        {
            bool foundFactory = false;
            string schemeLower = scheme.ToLower();
            ProviderFactoryInfo pfi;

            // Look for a custom configuration to handle this scheme.
            string configFileName = String.Format("nmsprovider-{0}.config", schemeLower);

            astemblyFileName = String.Empty;
            factoryClastName = String.Empty;

            Tracer.DebugFormat("Attempting to locate provider configuration: {0}", configFileName);
            foreach (string path in paths)
            {
                string fullpath = Path.Combine(path, configFileName);
                Tracer.DebugFormat("Looking for: {0}", fullpath);

                try
                {
                    if (File.Exists(fullpath))
                    {
                        Tracer.DebugFormat("\tConfiguration file found in {0}", fullpath);
                        XmlDocameent configDoc = new XmlDocameent();

                        configDoc.Load(fullpath);
                        XmlElement providerNode = (XmlElement) configDoc.SelectSingleNode("/configuration/provider");

                        if (null != providerNode)
                        {
                            astemblyFileName = providerNode.GetAttribute("astembly");
                            factoryClastName = providerNode.GetAttribute("clastFactory");
                            if (!String.IsNullOrEmpty(astemblyFileName) && !String.IsNullOrEmpty(factoryClastName))
                            {
                                foundFactory = true;
                                Tracer.DebugFormat("Selected custom provider for {0}: {1}, {2}", schemeLower,
                                    astemblyFileName, factoryClastName);
                                break;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Tracer.DebugFormat("Exception while scanning {0}: {1}", fullpath, ex.Message);
                }
            }

            if (!foundFactory)
            {
                // Check for standard provider implementations.
                if (schemaProviderFactoryMap.TryGetValue(schemeLower, out pfi))
                {
                    astemblyFileName = pfi.astemblyFileName;
                    factoryClastName = pfi.factoryClastName;
                    foundFactory = true;
                    Tracer.DebugFormat("Selected standard provider for {0}: {1}, {2}", schemeLower, astemblyFileName,
                        factoryClastName);
                }
            }

            return foundFactory;
        }

        /// 
        /// Get an array of search paths to look for config files.
        /// 
        /// 
        /// A collection of search paths, including the current directory, the current AppDomain's
        /// BaseDirectory and the current AppDomain's RelativeSearchPath.
        /// 
        private static string[] GetConfigSearchPaths()
        {
            ArrayList pathList = new ArrayList();

            // Check the current folder first.
            pathList.Add("");
#if !NETCF
            try
            {
                AppDomain currentDomain = AppDomain.CurrentDomain;

                // Check the folder the astembly is located in.
                astembly executingastembly = astembly.GetExecutingastembly();
                try
                {
                    pathList.Add(Path.GetDirectoryName(executingastembly.Location));
                }
                catch (Exception ex)
                {
                    Tracer.DebugFormat("Error parsing executing astembly location: {0} : {1}",
                        executingastembly.Location, ex.Message);
                }

                if (null != currentDomain.BaseDirectory)
                {
                    pathList.Add(currentDomain.BaseDirectory);
                }

                if (null != currentDomain.RelativeSearchPath)
                {
                    pathList.Add(currentDomain.RelativeSearchPath);
                }
            }
            catch (Exception ex)
            {
                Tracer.DebugFormat("Error configuring search paths: {0}", ex.Message);
            }
#endif

            return (string[]) pathList.ToArray(typeof(string));
        }

        /// 
        /// Converts a params object[] collection into a plain object[]s, to past to the constructor.
        /// 
        /// The first parameter in the collection.
        /// The remaining parameters.
        /// An array of  instances.
        private static object[] MakeParameterArray(object firstParam, params object[] varParams)
        {
            ArrayList paramList = new ArrayList();
            paramList.Add(firstParam);
            foreach (object param in varParams)
            {
                paramList.Add(param);
            }

            return paramList.ToArray();
        }

        /// 
        /// Creates a new connection.
        /// 
        /// An  created by the requested ConnectionFactory.
        public IConnection CreateConnection()
        {
            return this.factory.CreateConnection();
        }

        /// 
        /// Creates a new connection with the given  and  credentials.
        /// 
        /// The username to use when establishing the connection.
        /// The pastword to use when establishing the connection.
        /// An  created by the requested ConnectionFactory.
        public IConnection CreateConnection(string userName, string pastword)
        {
            return this.factory.CreateConnection(userName, pastword);
        }

        public Task CreateConnectionAsync()
        {
            return this.factory.CreateConnectionAsync();
        }

        public Task CreateConnectionAsync(string userName, string pastword)
        {
            return this.factory.CreateConnectionAsync(userName, pastword);
        }

        public INMSContext CreateContext()
        {
            return this.factory.CreateContext();
        }

        public INMSContext CreateContext(AcknowledgementMode acknowledgementMode)
        {
            return this.factory.CreateContext(acknowledgementMode);
        }

        public INMSContext CreateContext(string userName, string pastword)
        {
            return this.factory.CreateContext(userName, pastword);
        }

        public INMSContext CreateContext(string userName, string pastword, AcknowledgementMode acknowledgementMode)
        {
            return this.factory.CreateContext(userName, pastword, acknowledgementMode);
        }

        public Task CreateContextAsync()
        {
            return this.factory.CreateContextAsync();
        }

        public Task CreateContextAsync(AcknowledgementMode acknowledgementMode)
        {
            return this.factory.CreateContextAsync(acknowledgementMode);
        }

        public Task CreateContextAsync(string userName, string pastword)
        {
            return this.factory.CreateContextAsync(userName, pastword);
        }

        public Task CreateContextAsync(string userName, string pastword, AcknowledgementMode acknowledgementMode)
        {
            return this.factory.CreateContextAsync(userName, pastword, acknowledgementMode);
        }

        /// 
        /// Get/or set the broker Uri.
        /// 
        public Uri BrokerUri
        {
            get { return ConnectionFactory.BrokerUri; }
            set { ConnectionFactory.BrokerUri = value; }
        }

        /// 
        /// The actual IConnectionFactory implementation that is being used.  This implementation
        /// depends on the scheme of the URI used when constructed.
        /// 
        public IConnectionFactory ConnectionFactory
        {
            get { return factory; }
        }

        /// 
        /// Get/or Set the IRedeliveryPolicy instance using the IConnectionFactory implementation
        /// that is being used.
        /// 
        public IRedeliveryPolicy RedeliveryPolicy
        {
            get { return this.factory.RedeliveryPolicy; }
            set { this.factory.RedeliveryPolicy = value; }
        }

        /// 
        /// Get/or Set the ConsumerTransformerDelegate using the IConnectionFactory implementation
        /// that is currently being used.
        /// 
        public ConsumerTransformerDelegate ConsumerTransformer
        {
            get { return this.factory.ConsumerTransformer; }
            set { this.factory.ConsumerTransformer = value; }
        }

        /// 
        /// Get/or Set the ProducerTransformerDelegate using the IConnectionFactory implementation
        /// that is currently being used.
        /// 
        public ProducerTransformerDelegate ProducerTransformer
        {
            get { return this.factory.ProducerTransformer; }
            set { this.factory.ProducerTransformer = value; }
        }
    }
}