using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Reflection; using ScrewTurn.Wiki.PluginFramework; using System.Globalization; namespace ScrewTurn.Wiki { /// /// Loads providers from assemblies. /// public static class ProviderLoader { // These must be const because they are used in switch constructs internal const string UsersProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IUsersStorageProviderV30"; internal const string PagesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IPagesStorageProviderV30"; internal const string FilesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFilesStorageProviderV30"; internal const string FormatterProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFormatterProviderV30"; internal const string CacheProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.ICacheProviderV30"; internal static string SettingsStorageProviderAssemblyName = ""; /// /// Verifies the read-only/read-write constraints of providers. /// /// The type of the provider. /// The provider. /// Thrown when a constraint is not fulfilled. private static void VerifyConstraints(T provider) { if(typeof(T) == typeof(IUsersStorageProviderV30)) { // If the provider allows to write user accounts data, then group membership must be writeable too IUsersStorageProviderV30 actualInstance = (IUsersStorageProviderV30)provider; if(!actualInstance.UserAccountsReadOnly && actualInstance.GroupMembershipReadOnly) { throw new ProviderConstraintException("If UserAccountsReadOnly is false, then also GroupMembershipReadOnly must be false"); } } } /// /// Tries to inizialize a provider. /// /// The type of the provider, which must implement IProvider. /// The provider instance to initialize. /// The collector for enabled providers. /// The collector for disabled providers. private static void Initialize(T instance, ProviderCollector collectorEnabled, ProviderCollector collectorDisabled) where T : class, IProviderV30 { if(collectorEnabled.GetProvider(instance.GetType().FullName) != null || collectorDisabled.GetProvider(instance.GetType().FullName) != null) { Log.LogEntry("Provider " + instance.Information.Name + " already in memory", EntryType.Warning, Log.SystemUsername); return; } bool enabled = !IsDisabled(instance.GetType().FullName); try { if(enabled) { instance.Init(Host.Instance, LoadConfiguration(instance.GetType().FullName)); } } catch(InvalidConfigurationException) { // Disable Provider enabled = false; Log.LogEntry("Unable to load provider " + instance.Information.Name + " (configuration rejected), disabling it", EntryType.Error, Log.SystemUsername); SaveStatus(instance.GetType().FullName, false); } catch { // Disable Provider enabled = false; Log.LogEntry("Unable to load provider " + instance.Information.Name + " (unknown error), disabling it", EntryType.Error, Log.SystemUsername); SaveStatus(instance.GetType().FullName, false); throw; // Exception is rethrown because it's not a normal condition } if(enabled) collectorEnabled.AddProvider(instance); else collectorDisabled.AddProvider(instance); // Verify constraints VerifyConstraints(instance); Log.LogEntry("Provider " + instance.Information.Name + " loaded (" + (enabled ? "Enabled" : "Disabled") + ")", EntryType.General, Log.SystemUsername); } /// /// Loads all the Providers and initialises them. /// /// A value indicating whether to load users storage providers. /// A value indicating whether to load pages storage providers. /// A value indicating whether to load files storage providers. /// A value indicating whether to load formatter providers. /// A value indicating whether to load cache providers. public static void FullLoad(bool loadUsers, bool loadPages, bool loadFiles, bool loadFormatters, bool loadCache) { string[] pluginAssemblies = Settings.Provider.ListPluginAssemblies(); List users = new List(2); List dUsers = new List(2); List pages = new List(2); List dPages = new List(2); List files = new List(2); List dFiles = new List(2); List forms = new List(2); List dForms = new List(2); List cache = new List(2); List dCache = new List(2); for(int i = 0; i < pluginAssemblies.Length; i++) { IFilesStorageProviderV30[] d; IUsersStorageProviderV30[] u; IPagesStorageProviderV30[] p; IFormatterProviderV30[] f; ICacheProviderV30[] c; LoadFrom(pluginAssemblies[i], out u, out p, out d, out f, out c); if(loadFiles) files.AddRange(d); if(loadUsers) users.AddRange(u); if(loadPages) pages.AddRange(p); if(loadFormatters) forms.AddRange(f); if(loadCache) cache.AddRange(c); } // Init and add to the Collectors, starting from files providers for(int i = 0; i < files.Count; i++) { Initialize(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector); } for(int i = 0; i < users.Count; i++) { Initialize(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector); } for(int i = 0; i < pages.Count; i++) { Initialize(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector); } for(int i = 0; i < forms.Count; i++) { Initialize(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector); } for(int i = 0; i < cache.Count; i++) { Initialize(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector); } } /// /// Loads the Configuration data of a Provider. /// /// The Type Name of the Provider. /// The Configuration, if available, otherwise an empty string. public static string LoadConfiguration(string typeName) { return Settings.Provider.GetPluginConfiguration(typeName); } /// /// Saves the Configuration data of a Provider. /// /// The Type Name of the Provider. /// The Configuration data to save. public static void SaveConfiguration(string typeName, string config) { Settings.Provider.SetPluginConfiguration(typeName, config); } /// /// Saves the Status of a Provider. /// /// The Type Name of the Provider. /// A value specifying whether or not the Provider is enabled. public static void SaveStatus(string typeName, bool enabled) { Settings.Provider.SetPluginStatus(typeName, enabled); } /// /// Returns a value specifying whether or not a Provider is disabled. /// /// The Type Name of the Provider. /// True if the Provider is disabled. public static bool IsDisabled(string typeName) { return !Settings.Provider.GetPluginStatus(typeName); } /// /// Loads Providers from an assembly. /// /// The path of the Assembly to load the Providers from. public static int LoadFromAuto(string assembly) { IUsersStorageProviderV30[] users; IPagesStorageProviderV30[] pages; IFilesStorageProviderV30[] files; IFormatterProviderV30[] forms; ICacheProviderV30[] cache; LoadFrom(assembly, out users, out pages, out files, out forms, out cache); int count = 0; // Init and add to the Collectors, starting from files providers for(int i = 0; i < files.Length; i++) { Initialize(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector); count++; } for(int i = 0; i < users.Length; i++) { Initialize(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector); count++; } for(int i = 0; i < pages.Length; i++) { Initialize(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector); count++; } for(int i = 0; i < forms.Length; i++) { Initialize(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector); count++; } for(int i = 0; i < cache.Length; i++) { Initialize(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector); count++; } return count; } /// /// Loads Providers from an assembly. /// /// The path of the Assembly to load the Providers from. /// The Users Providers. /// The Files Providers. /// The Pages Providers. /// The Formatter Providers. /// The Cache Providers. /// The Components returned are not initialized. public static void LoadFrom(string assembly, out IUsersStorageProviderV30[] users, out IPagesStorageProviderV30[] pages, out IFilesStorageProviderV30[] files, out IFormatterProviderV30[] formatters, out ICacheProviderV30[] cache) { Assembly asm = null; try { //asm = Assembly.LoadFile(assembly); // This way the DLL is not locked and can be deleted at runtime asm = Assembly.Load(LoadAssemblyFromProvider(Path.GetFileName(assembly))); } catch { files = new IFilesStorageProviderV30[0]; users = new IUsersStorageProviderV30[0]; pages = new IPagesStorageProviderV30[0]; formatters = new IFormatterProviderV30[0]; cache = new ICacheProviderV30[0]; Log.LogEntry("Unable to load assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, Log.SystemUsername); return; } Type[] types = null; try { types = asm.GetTypes(); } catch(ReflectionTypeLoadException) { files = new IFilesStorageProviderV30[0]; users = new IUsersStorageProviderV30[0]; pages = new IPagesStorageProviderV30[0]; formatters = new IFormatterProviderV30[0]; cache = new ICacheProviderV30[0]; Log.LogEntry("Unable to load providers from (probably v2) assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, Log.SystemUsername); return; } List urs = new List(); List pgs = new List(); List fls = new List(); List frs = new List(); List che = new List(); Type[] interfaces; for(int i = 0; i < types.Length; i++) { // Avoid to load abstract classes as they cannot be instantiated if(types[i].IsAbstract) continue; interfaces = types[i].GetInterfaces(); foreach(Type iface in interfaces) { if(iface == typeof(IUsersStorageProviderV30)) { IUsersStorageProviderV30 tmpu = CreateInstance(asm, types[i]); if(tmpu != null) { urs.Add(tmpu); Collectors.FileNames[tmpu.GetType().FullName] = assembly; } } if(iface == typeof(IPagesStorageProviderV30)) { IPagesStorageProviderV30 tmpp = CreateInstance(asm, types[i]); if(tmpp != null) { pgs.Add(tmpp); Collectors.FileNames[tmpp.GetType().FullName] = assembly; } } if(iface == typeof(IFilesStorageProviderV30)) { IFilesStorageProviderV30 tmpd = CreateInstance(asm, types[i]); if(tmpd != null) { fls.Add(tmpd); Collectors.FileNames[tmpd.GetType().FullName] = assembly; } } if(iface == typeof(IFormatterProviderV30)) { IFormatterProviderV30 tmpf = CreateInstance(asm, types[i]); if(tmpf != null) { frs.Add(tmpf); Collectors.FileNames[tmpf.GetType().FullName] = assembly; } } if(iface == typeof(ICacheProviderV30)) { ICacheProviderV30 tmpc = CreateInstance(asm, types[i]); if(tmpc != null) { che.Add(tmpc); Collectors.FileNames[tmpc.GetType().FullName] = assembly; } } } } users = urs.ToArray(); pages = pgs.ToArray(); files = fls.ToArray(); formatters = frs.ToArray(); cache = che.ToArray(); } /// /// Creates an instance of a type implementing a provider interface. /// /// The provider interface type. /// The assembly that contains the type. /// The type to create an instance of. /// The instance, or null. private static T CreateInstance(Assembly asm, Type type) where T : class, IProviderV30 { T instance; try { instance = asm.CreateInstance(type.ToString()) as T; return instance; } catch { Log.LogEntry("Unable to create instance of " + type.ToString(), EntryType.Error, Log.SystemUsername); throw; } } /// /// Loads the content of an assembly from disk. /// /// The assembly file full path. /// The content of the assembly, in a byte array form. private static byte[] LoadAssemblyFromDisk(string assembly) { return File.ReadAllBytes(assembly); } /// /// Loads the content of an assembly from the settings provider. /// /// The name of the assembly, such as "Assembly.dll". /// The content fo the assembly. private static byte[] LoadAssemblyFromProvider(string assemblyName) { return Settings.Provider.RetrievePluginAssembly(assemblyName); } /// /// Loads the proper Setting Storage Provider, given its name. /// /// The fully qualified name (such as "Namespace.ProviderClass, MyAssembly"), or null/String.Empty/"default" for the default provider. /// The settings storage provider. public static ISettingsStorageProviderV30 LoadSettingsStorageProvider(string name) { if(name == null || name.Length == 0 || string.Compare(name, "default", true, CultureInfo.InvariantCulture) == 0) { return new SettingsStorageProvider(); } ISettingsStorageProviderV30 result = null; Exception inner = null; if(name.Contains(",")) { string[] fields = name.Split(','); if(fields.Length == 2) { fields[0] = fields[0].Trim(' ', '"'); fields[1] = fields[1].Trim(' ', '"'); try { // assemblyName should be an absolute path or a relative path in bin or public\Plugins Assembly asm; Type t; string assemblyName = fields[1]; if(!assemblyName.ToLowerInvariant().EndsWith(".dll")) assemblyName += ".dll"; if(File.Exists(assemblyName)) { asm = Assembly.Load(LoadAssemblyFromDisk(assemblyName)); t = asm.GetType(fields[0]); SettingsStorageProviderAssemblyName = Path.GetFileName(assemblyName); } else { string tentativePluginsPath = null; try { // Settings.PublicDirectory is only available when running the web app tentativePluginsPath = Path.Combine(Settings.PublicDirectory, "Plugins"); tentativePluginsPath = Path.Combine(tentativePluginsPath, assemblyName); } catch { } if(!string.IsNullOrEmpty(tentativePluginsPath) && File.Exists(tentativePluginsPath)) { asm = Assembly.Load(LoadAssemblyFromDisk(tentativePluginsPath)); t = asm.GetType(fields[0]); SettingsStorageProviderAssemblyName = Path.GetFileName(tentativePluginsPath); } else { // Trim .dll t = Type.GetType(fields[0] + "," + assemblyName.Substring(0, assemblyName.Length - 4), true, true); SettingsStorageProviderAssemblyName = assemblyName; } } result = t.GetConstructor(new Type[0]).Invoke(new object[0]) as ISettingsStorageProviderV30; } catch(Exception ex) { inner = ex; result = null; } } } if(result == null) throw new ArgumentException("Could not load the specified Settings Storage Provider", inner); else return result; } /// /// Loads all settings storage providers available in all DLLs stored in a provider. /// /// The input provider. /// The providers found (not initialized). public static ISettingsStorageProviderV30[] LoadAllSettingsStorageProviders(ISettingsStorageProviderV30 repository) { // This method is actually a memory leak because it can be executed multimple times // Every time it loads a set of assemblies which cannot be unloaded (unless a separate AppDomain is used) List result = new List(); foreach(string dll in repository.ListPluginAssemblies()) { byte[] asmBin = repository.RetrievePluginAssembly(dll); Assembly asm = Assembly.Load(asmBin); Type[] types = null; try { types = asm.GetTypes(); } catch(ReflectionTypeLoadException) { // Skip assembly Log.LogEntry("Unable to load providers from (probably v2) assembly " + Path.GetFileNameWithoutExtension(dll), EntryType.Error, Log.SystemUsername); continue; } foreach(Type type in types) { // Avoid to load abstract classes as they cannot be instantiated if(type.IsAbstract) continue; Type[] interfaces = type.GetInterfaces(); foreach(Type iface in interfaces) { if(iface == typeof(ISettingsStorageProviderV30)) { try { ISettingsStorageProviderV30 temp = asm.CreateInstance(type.ToString()) as ISettingsStorageProviderV30; if(temp != null) result.Add(temp); } catch { } } } } } return result.ToArray(); } /// /// Tries to change a provider's configuration. /// /// The provider. /// The new configuration. /// The error message, if any. /// true if the configuration is saved, false if the provider rejected it. public static bool TryChangeConfiguration(string typeName, string configuration, out string error) { error = null; bool enabled, canDisable; IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable); try { provider.Init(Host.Instance, configuration); } catch(InvalidConfigurationException icex) { error = icex.Message; return false; } SaveConfiguration(typeName, configuration); return true; } /// /// Disables a provider. /// /// The provider to disable. public static void DisableProvider(string typeName) { bool enabled, canDisable; IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable); if(enabled && canDisable) { provider.Shutdown(); Collectors.TryDisable(typeName); SaveStatus(typeName, false); } } /// /// Enables a provider. /// /// The provider to enable. public static void EnableProvider(string typeName) { bool enabled, canDisable; IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable); if(!enabled) { provider.Init(Host.Instance, LoadConfiguration(typeName)); Collectors.TryEnable(typeName); SaveStatus(typeName, true); } } /// /// Unloads a provider from memory. /// /// The provider to unload. public static void UnloadProvider(string typeName) { DisableProvider(typeName); Collectors.TryUnload(typeName); } } /// /// Defines an exception thrown when a constraint is not fulfilled by a provider. /// public class ProviderConstraintException : Exception { /// /// Initializes a new instance of the class. /// /// The message. public ProviderConstraintException(string message) : base(message) { } } }