#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 { /// /// Represents an entry in the Connection Provider Registry /// public class ConnectionProviderEntry { /// /// Gets or sets the name. /// /// The name. public string Name { get; private set; } /// /// Gets or sets the description. /// /// The description. public string Description { get; private set; } /// /// Gets or sets a value indicating whether this instance is multi platform. /// /// /// true if this instance is multi platform; otherwise, false. /// public bool IsMultiPlatform { get; private set; } /// /// Gets whether this provider has global connection state. This effectively indicates that subsequent connections after the first one /// created for this provider will re-use the same connection information and may/will disregard that values of the connection parameters /// you pass in /// public bool HasGlobalState { get; private set; } /// /// Gets the path of the assembly containing the provider implementation /// public string AssemblyPath { get; private set; } /// /// Initializes a new instance of the class. /// /// The name. /// The desc. /// The assembly path /// if set to true [multi platform]. internal ConnectionProviderEntry(string name, string desc, string asmPath, bool multiPlatform) { this.Name = name; this.Description = desc; this.IsMultiPlatform = multiPlatform; this.AssemblyPath = asmPath; } } /// /// A method that creates instances from the given parameters /// /// The init params. /// public delegate IServerConnection ConnectionFactoryMethod(NameValueCollection initParams); /// /// /// The entry point of the Maestro API. The is used to create /// objects. is the root object of the Maestro API, and is where most of the functionality provided /// by this API is accessed from. /// /// /// The supports dynamic creation of 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: /// /// /// The name of the provider /// The assembly containing the implementation /// The name of this implementation /// /// /// The implementation is expected to have a non-public constructor which takes a single parameter, /// a containing the initialization parameters parsed from the given connection /// string. /// /// /// /// This example shows how to create a http-based MapGuide Server connection to the server's mapagent interface. /// /// using OSGeo.MapGuide.MaestroAPI; /// /// ... /// /// IServerConnection conn = ConnectionProviderRegistry.CreateConnection("Maestro.Http", /// "Url", "http://localhost/mapguide/mapagent/mapagent.fcgi", /// "Username", "Administrator", /// "Password", "admin"); /// /// /// /// /// This example shows how to create a TCP/IP connection that wraps the official MapGuide API /// /// using OSGeo.MapGuide.MaestroAPI; /// /// ... /// /// IServerConnection conn = ConnectionProviderRegistry.CreateConnection("Maestro.LocalNative", /// "ConfigFile", "webconfig.ini", /// "Username", "Administrator", /// "Password", "admin"); /// /// public sealed class ConnectionProviderRegistry { const string PROVIDER_CONFIG = "ConnectionProviders.xml"; //NOXLATE static Dictionary _ctors; static List _providers; static Dictionary _callCount; static string _dllRoot; static ConnectionProviderRegistry() { _ctors = new Dictionary(); _providers = new List(); _callCount = new Dictionary(); 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"); //NOXLATE foreach (XmlNode prov in providers) { string name = prov["Name"].InnerText.ToUpper(); //NOXLATE string desc = prov["Description"].InnerText; //NOXLATE string dll = prov["Assembly"].InnerText; //NOXLATE string type = prov["Type"].InnerText; //NOXLATE 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; var impl = attr[0].ImplType; _ctors[name] = new ConnectionFactoryMethod((initParams) => { BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance; IServerConnection conn = (IServerConnection)impl.InvokeMember(null, flags, null, null, new object[] { initParams }); return conn; }); _providers.Add(new ConnectionProviderEntry(name, desc, dll, attr[0].IsMultiPlatform)); _callCount[name] = 0; } } catch { } } } /// /// Parses the given Maestro connection string into a /// /// /// public 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; } /// /// Gets a list of registered provider names. The returned names are in upper-case. /// /// public static ConnectionProviderEntry[] GetProviders() { return _providers.ToArray(); } /// /// Gets the invocation count for the given provider (the number of times a connection has been created for that provider) /// /// /// This (in conjunction with the property) can /// be used to programmatically determine if creating a connection for a given provider will respect the connection parameter values /// you pass to it. (0 calls will respect your parameter values. 1 or more will not) /// /// /// The invocation count for the given provider. -1 if the provider is not registered or does not exist public static int GetInvocationCount(string provider) { if (_callCount.ContainsKey(provider.ToUpper())) return _callCount[provider.ToUpper()]; return -1; } /// /// Registers a new connection provider /// /// The provider entry. /// The factory method. public static void RegisterProvider(ConnectionProviderEntry entry, ConnectionFactoryMethod method) { string name = entry.Name.ToUpper(); if (_ctors.ContainsKey(name)) throw new ArgumentException(string.Format(Strings.ErrorProviderAlreadyRegistered, entry.Name)); _ctors[name] = method; _providers.Add(entry); } /// /// Creates an initialized object given the provider name and connection string /// /// /// /// /// The Maestro.Local provider (that wraps mg-desktop) and Maestro.LocalNative providers (that wraps the official MapGuide API) /// are unique in that it has global connection state. What this means is that subsequent connections after the first one for /// these providers may re-use existing state for the first connection. The reason for this is that creating this connection /// internally calls MgdPlatform.Initialize(iniFile) and MapGuideApi.MgInitializeWebTier(iniFile) respectively, that initializes /// the necessary library parameters in the process space of your application. Creating another connection will call /// MgdPlatform.Initialize and MapGuideApi.MgInitializeWebTier again, but these methods are by-design only made to be called once /// as subsequent calls are returned immediately. /// /// Basically, the connection parameters you pass in are for initializing the provider the first time round. Subsequent calls may not /// (most likely will not) respect the values of your connection parameters. /// /// You can programmatically check this via the property /// /// public static IServerConnection CreateConnection(string provider, string connectionString) { string name = provider.ToUpper(); if (!_ctors.ContainsKey(name)) throw new ArgumentException(string.Format(Strings.ErrorProviderNotRegistered, provider)); ConnectionProviderEntry prv = FindProvider(provider); if (prv != null && !prv.IsMultiPlatform && Platform.IsRunningOnMono) throw new NotSupportedException(string.Format(Strings.ErrorProviderNotUsableForYourPlatform, provider)); ConnectionFactoryMethod method = _ctors[name]; NameValueCollection initParams = ParseConnectionString(connectionString); IServerConnection result = method(initParams); _callCount[name]++; return result; } /// /// Creates an initialized object given the provider name and the initalization parameters /// /// /// /// public static IServerConnection CreateConnection(string provider, NameValueCollection connInitParams) { string name = provider.ToUpper(); if (!_ctors.ContainsKey(name)) throw new ArgumentException(string.Format(Strings.ErrorProviderNotRegistered, provider)); ConnectionProviderEntry prv = FindProvider(provider); if (prv != null && !prv.IsMultiPlatform && Platform.IsRunningOnMono) throw new NotSupportedException(string.Format(Strings.ErrorProviderNotUsableForYourPlatform, provider)); var method = _ctors[name]; IServerConnection result = method(connInitParams); _callCount[name]++; return result; } /// /// Creates an initialized object given the provider name and the initalization parameters. /// /// /// A variable list of initialization parameters. They must be specified in the form: [Param1], [Value1], [Param2], [Value2], etc /// 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); } public static ConnectionProviderEntry FindProvider(string provider) { string cmp = provider.ToUpper(); foreach (var prv in _providers) { if (prv.Name == cmp) return prv; } return null; } } }