#region Disclaimer / License // Copyright (C) 2010, Jackie Ng // http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Reflection; using System.Collections.Specialized; using System.Data.Common; namespace OSGeo.MapGuide.MaestroAPI { /// <summary> /// Represents an entry in the Connection Provider Registry /// </summary> public class ConnectionProviderEntry { /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> public string Name { get; private set; } /// <summary> /// Gets or sets the description. /// </summary> /// <value>The description.</value> public string Description { get; private set; } /// <summary> /// Gets or sets a value indicating whether this instance is multi platform. /// </summary> /// <value> /// <c>true</c> if this instance is multi platform; otherwise, <c>false</c>. /// </value> public bool IsMultiPlatform { get; private set; } internal ConnectionProviderEntry(string name, string desc, bool multiPlatform) { this.Name = name; this.Description = desc; this.IsMultiPlatform = multiPlatform; } } /// <summary> /// The entry point of the Maestro API. The <see cref="ConnectionProviderRegistry"/> is used to create <see cref="IServerConnection"/> /// objects. <see cref="IServerConnection"/> is the root object of the Maestro API, and is where most of the functionality provided /// by this API is accessed from. /// /// The <see cref="ConnectionProviderRegistry"/> supports dynamic creation of <see cref="IServerConnection"/> objects given a provider name /// and a connection string, which specifies the initialization parameters of the connection. The connection providers are defined in an XML /// file called ConnectionProviders.xml which contains all the registered providers. Each provider has the following properties: /// /// 1. The name of the provider /// 2. The assembly containing the <see cref="IServerConnection"/> implementation /// 3. The name of this <see cref="IServerConnection"/> implementation. /// /// The <see cref="IServerConnection"/> implementation is expected to have a non-public constructor which takes a single parameter, /// a <see cref="System.Collections.Specialized.NameValueCollection"/> containing the initialization parameters parsed from the given connection /// string. /// </summary> /// <example> /// This example shows how to create a http-based MapGuide Server connection to the server's mapagent interface. /// <code> /// using OSGeo.MapGuide.MaestroAPI; /// /// ... /// /// IServerConnection conn = ConnectionProviderRegistry.CreateConnection("Maestro.Http", /// "Url", "http://localhost/mapguide/mapagent/mapagent.fcgi", /// "Username", "Administrator", /// "Password", "admin"); /// /// </code> /// </example> /// <example> /// This example shows how to create a TCP/IP connection that wraps the official MapGuide API /// <code> /// using OSGeo.MapGuide.MaestroAPI; /// /// ... /// /// IServerConnection conn = ConnectionProviderRegistry.CreateConnection("Maestro.LocalNative", /// "ConfigFile", "webconfig.ini", /// "Username", "Administrator", /// "Password", "admin"); /// </code> /// </example> public sealed class ConnectionProviderRegistry { const string PROVIDER_CONFIG = "ConnectionProviders.xml"; static Dictionary<string, Type> _ctors; static List<ConnectionProviderEntry> _providers; static string _dllRoot; static ConnectionProviderRegistry() { _ctors = new Dictionary<string, Type>(); _providers = new List<ConnectionProviderEntry>(); var dir = System.IO.Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); var path = System.IO.Path.Combine(dir, PROVIDER_CONFIG); _dllRoot = System.IO.Path.GetDirectoryName(path); XmlDocument doc = new XmlDocument(); doc.Load(path); XmlNodeList providers = doc.SelectNodes("//ConnectionProviderRegistry/ConnectionProvider"); foreach (XmlNode prov in providers) { string name = prov["Name"].InnerText.ToUpper(); string desc = prov["Description"].InnerText; string dll = prov["Assembly"].InnerText; string type = prov["Type"].InnerText; if (!System.IO.Path.IsPathRooted(dll)) dll = System.IO.Path.Combine(_dllRoot, dll); try { Assembly asm = Assembly.LoadFrom(dll); MaestroApiProviderAttribute[] attr = asm.GetCustomAttributes(typeof(MaestroApiProviderAttribute), true) as MaestroApiProviderAttribute[]; if (attr != null && attr.Length == 1) { name = attr[0].Name.ToUpper(); desc = attr[0].Description; _ctors[name] = attr[0].ImplType; _providers.Add(new ConnectionProviderEntry(name, desc, attr[0].IsMultiPlatform)); } } catch { } } } internal static NameValueCollection ParseConnectionString(string connectionString) { var builder = new DbConnectionStringBuilder(); builder.ConnectionString = connectionString; NameValueCollection values = new NameValueCollection(); foreach (string key in builder.Keys) { values.Add(key, builder[key].ToString()); } return values; } /// <summary> /// Gets a list of registered provider names. The returned names are in upper-case. /// </summary> /// <returns></returns> public static ConnectionProviderEntry[] GetProviders() { return _providers.ToArray(); } /// <summary> /// Creates an initialized <see cref="IServerConnection"/> object given the provider name and connection string /// </summary> /// <param name="provider"></param> /// <param name="connectionString"></param> /// <returns></returns> public static IServerConnection CreateConnection(string provider, string connectionString) { string name = provider.ToUpper(); if (!_ctors.ContainsKey(name)) throw new ArgumentException("Provider not registered: " + provider); ConnectionProviderEntry prv = FindProvider(provider); if (prv != null && !prv.IsMultiPlatform && Platform.IsRunningOnMono) throw new NotSupportedException("The specified provider is not usable in your operating system"); Type t = _ctors[name]; NameValueCollection initParams = ParseConnectionString(connectionString); BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance; IServerConnection conn = (IServerConnection)t.InvokeMember(null, flags, null, null, new object[] { initParams }); return conn; } /// <summary> /// Creates an initialized <see cref="IServerConnection"/> object given the provider name and the initalization parameters /// </summary> /// <param name="provider"></param> /// <param name="connInitParams"></param> /// <returns></returns> public static IServerConnection CreateConnection(string provider, NameValueCollection connInitParams) { string name = provider.ToUpper(); if (!_ctors.ContainsKey(name)) throw new ArgumentException("Provider not registered: " + provider); ConnectionProviderEntry prv = FindProvider(provider); if (prv != null && !prv.IsMultiPlatform && Platform.IsRunningOnMono) throw new NotSupportedException("The specified provider is not usable in your operating system"); Type t = _ctors[name]; BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance; IServerConnection conn = (IServerConnection)t.InvokeMember(null, flags, null, null, new object[] { connInitParams }); return conn; } /// <summary> /// Creates an initialized <see cref="IServerConnection"/> object given the provider name and the initalization parameters. /// </summary> /// <param name="provider"></param> /// <param name="initParameters">A variable list of initialization parameters. They must be specified in the form: [Param1], [Value1], [Param2], [Value2], etc</param> /// <returns></returns> public static IServerConnection CreateConnection(string provider, params string[] initParameters) { var initP = new NameValueCollection(); for (int i = 0; i < initParameters.Length; i += 2) { string name = null; string value = null; if (i < initParameters.Length - 1) name = initParameters[i]; if (i + 1 <= initParameters.Length - 1) value = initParameters[i + 1]; if (name != null) initP[name] = value ?? string.Empty; } return CreateConnection(provider, initP); } private static ConnectionProviderEntry FindProvider(string provider) { foreach (var prv in _providers) { if (prv.Name == provider) return prv; } return null; } } }