screwturn-4/Core/ProviderLoader.cs
2009-09-30 13:47:13 +00:00

556 lines
22 KiB
C#

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 {
/// <summary>
/// Loads providers from assemblies.
/// </summary>
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 = "";
/// <summary>
/// Verifies the read-only/read-write constraints of providers.
/// </summary>
/// <typeparam name="T">The type of the provider.</typeparam>
/// <param name="provider">The provider.</param>
/// <exception cref="T:ProviderConstraintException">Thrown when a constraint is not fulfilled.</exception>
private static void VerifyConstraints<T>(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");
}
}
}
/// <summary>
/// Tries to inizialize a provider.
/// </summary>
/// <typeparam name="T">The type of the provider, which must implement <b>IProvider</b>.</typeparam>
/// <param name="instance">The provider instance to initialize.</param>
/// <param name="collectorEnabled">The collector for enabled providers.</param>
/// <param name="collectorDisabled">The collector for disabled providers.</param>
private static void Initialize<T>(T instance, ProviderCollector<T> collectorEnabled,
ProviderCollector<T> 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<T>(instance);
Log.LogEntry("Provider " + instance.Information.Name + " loaded (" + (enabled ? "Enabled" : "Disabled") + ")", EntryType.General, Log.SystemUsername);
}
/// <summary>
/// Loads all the Providers and initialises them.
/// </summary>
/// <param name="loadUsers">A value indicating whether to load users storage providers.</param>
/// <param name="loadPages">A value indicating whether to load pages storage providers.</param>
/// <param name="loadFiles">A value indicating whether to load files storage providers.</param>
/// <param name="loadFormatters">A value indicating whether to load formatter providers.</param>
/// <param name="loadCache">A value indicating whether to load cache providers.</param>
public static void FullLoad(bool loadUsers, bool loadPages, bool loadFiles, bool loadFormatters, bool loadCache) {
string[] pluginAssemblies = Settings.Provider.ListPluginAssemblies();
List<IUsersStorageProviderV30> users = new List<IUsersStorageProviderV30>(2);
List<IUsersStorageProviderV30> dUsers = new List<IUsersStorageProviderV30>(2);
List<IPagesStorageProviderV30> pages = new List<IPagesStorageProviderV30>(2);
List<IPagesStorageProviderV30> dPages = new List<IPagesStorageProviderV30>(2);
List<IFilesStorageProviderV30> files = new List<IFilesStorageProviderV30>(2);
List<IFilesStorageProviderV30> dFiles = new List<IFilesStorageProviderV30>(2);
List<IFormatterProviderV30> forms = new List<IFormatterProviderV30>(2);
List<IFormatterProviderV30> dForms = new List<IFormatterProviderV30>(2);
List<ICacheProviderV30> cache = new List<ICacheProviderV30>(2);
List<ICacheProviderV30> dCache = new List<ICacheProviderV30>(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<IFilesStorageProviderV30>(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector);
}
for(int i = 0; i < users.Count; i++) {
Initialize<IUsersStorageProviderV30>(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector);
}
for(int i = 0; i < pages.Count; i++) {
Initialize<IPagesStorageProviderV30>(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector);
}
for(int i = 0; i < forms.Count; i++) {
Initialize<IFormatterProviderV30>(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector);
}
for(int i = 0; i < cache.Count; i++) {
Initialize<ICacheProviderV30>(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector);
}
}
/// <summary>
/// Loads the Configuration data of a Provider.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <returns>The Configuration, if available, otherwise an empty string.</returns>
public static string LoadConfiguration(string typeName) {
return Settings.Provider.GetPluginConfiguration(typeName);
}
/// <summary>
/// Saves the Configuration data of a Provider.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <param name="config">The Configuration data to save.</param>
public static void SaveConfiguration(string typeName, string config) {
Settings.Provider.SetPluginConfiguration(typeName, config);
}
/// <summary>
/// Saves the Status of a Provider.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <param name="enabled">A value specifying whether or not the Provider is enabled.</param>
public static void SaveStatus(string typeName, bool enabled) {
Settings.Provider.SetPluginStatus(typeName, enabled);
}
/// <summary>
/// Returns a value specifying whether or not a Provider is disabled.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <returns>True if the Provider is disabled.</returns>
public static bool IsDisabled(string typeName) {
return !Settings.Provider.GetPluginStatus(typeName);
}
/// <summary>
/// Loads Providers from an assembly.
/// </summary>
/// <param name="assembly">The path of the Assembly to load the Providers from.</param>
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<IFilesStorageProviderV30>(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector);
count++;
}
for(int i = 0; i < users.Length; i++) {
Initialize<IUsersStorageProviderV30>(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector);
count++;
}
for(int i = 0; i < pages.Length; i++) {
Initialize<IPagesStorageProviderV30>(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector);
count++;
}
for(int i = 0; i < forms.Length; i++) {
Initialize<IFormatterProviderV30>(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector);
count++;
}
for(int i = 0; i < cache.Length; i++) {
Initialize<ICacheProviderV30>(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector);
count++;
}
return count;
}
/// <summary>
/// Loads Providers from an assembly.
/// </summary>
/// <param name="assembly">The path of the Assembly to load the Providers from.</param>
/// <param name="users">The Users Providers.</param>
/// <param name="files">The Files Providers.</param>
/// <param name="pages">The Pages Providers.</param>
/// <param name="formatters">The Formatter Providers.</param>
/// <param name="cache">The Cache Providers.</param>
/// <remarks>The Components returned are <b>not</b> initialized.</remarks>
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<IUsersStorageProviderV30> urs = new List<IUsersStorageProviderV30>();
List<IPagesStorageProviderV30> pgs = new List<IPagesStorageProviderV30>();
List<IFilesStorageProviderV30> fls = new List<IFilesStorageProviderV30>();
List<IFormatterProviderV30> frs = new List<IFormatterProviderV30>();
List<ICacheProviderV30> che = new List<ICacheProviderV30>();
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<IUsersStorageProviderV30>(asm, types[i]);
if(tmpu != null) {
urs.Add(tmpu);
Collectors.FileNames[tmpu.GetType().FullName] = assembly;
}
}
if(iface == typeof(IPagesStorageProviderV30)) {
IPagesStorageProviderV30 tmpp = CreateInstance<IPagesStorageProviderV30>(asm, types[i]);
if(tmpp != null) {
pgs.Add(tmpp);
Collectors.FileNames[tmpp.GetType().FullName] = assembly;
}
}
if(iface == typeof(IFilesStorageProviderV30)) {
IFilesStorageProviderV30 tmpd = CreateInstance<IFilesStorageProviderV30>(asm, types[i]);
if(tmpd != null) {
fls.Add(tmpd);
Collectors.FileNames[tmpd.GetType().FullName] = assembly;
}
}
if(iface == typeof(IFormatterProviderV30)) {
IFormatterProviderV30 tmpf = CreateInstance<IFormatterProviderV30>(asm, types[i]);
if(tmpf != null) {
frs.Add(tmpf);
Collectors.FileNames[tmpf.GetType().FullName] = assembly;
}
}
if(iface == typeof(ICacheProviderV30)) {
ICacheProviderV30 tmpc = CreateInstance<ICacheProviderV30>(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();
}
/// <summary>
/// Creates an instance of a type implementing a provider interface.
/// </summary>
/// <typeparam name="T">The provider interface type.</typeparam>
/// <param name="asm">The assembly that contains the type.</param>
/// <param name="type">The type to create an instance of.</param>
/// <returns>The instance, or <c>null</c>.</returns>
private static T CreateInstance<T>(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;
}
}
/// <summary>
/// Loads the content of an assembly from disk.
/// </summary>
/// <param name="assembly">The assembly file full path.</param>
/// <returns>The content of the assembly, in a byte array form.</returns>
private static byte[] LoadAssemblyFromDisk(string assembly) {
return File.ReadAllBytes(assembly);
}
/// <summary>
/// Loads the content of an assembly from the settings provider.
/// </summary>
/// <param name="assemblyName">The name of the assembly, such as "Assembly.dll".</param>
/// <returns>The content fo the assembly.</returns>
private static byte[] LoadAssemblyFromProvider(string assemblyName) {
return Settings.Provider.RetrievePluginAssembly(assemblyName);
}
/// <summary>
/// Loads the proper Setting Storage Provider, given its name.
/// </summary>
/// <param name="name">The fully qualified name (such as "Namespace.ProviderClass, MyAssembly"), or <c>null</c>/<b>String.Empty</b>/"<b>default</b>" for the default provider.</param>
/// <returns>The settings storage provider.</returns>
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;
}
/// <summary>
/// Loads all settings storage providers available in all DLLs stored in a provider.
/// </summary>
/// <param name="repository">The input provider.</param>
/// <returns>The providers found (not initialized).</returns>
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<ISettingsStorageProviderV30> result = new List<ISettingsStorageProviderV30>();
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();
}
/// <summary>
/// Tries to change a provider's configuration.
/// </summary>
/// <param name="typeName">The provider.</param>
/// <param name="configuration">The new configuration.</param>
/// <param name="error">The error message, if any.</param>
/// <returns><c>true</c> if the configuration is saved, <c>false</c> if the provider rejected it.</returns>
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;
}
/// <summary>
/// Disables a provider.
/// </summary>
/// <param name="typeName">The provider to disable.</param>
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);
}
}
/// <summary>
/// Enables a provider.
/// </summary>
/// <param name="typeName">The provider to enable.</param>
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);
}
}
/// <summary>
/// Unloads a provider from memory.
/// </summary>
/// <param name="typeName">The provider to unload.</param>
public static void UnloadProvider(string typeName) {
DisableProvider(typeName);
Collectors.TryUnload(typeName);
}
}
/// <summary>
/// Defines an exception thrown when a constraint is not fulfilled by a provider.
/// </summary>
public class ProviderConstraintException : Exception {
/// <summary>
/// Initializes a new instance of the <see cref="T:ProviderConstraintException" /> class.
/// </summary>
/// <param name="message">The message.</param>
public ProviderConstraintException(string message)
: base(message) { }
}
}