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) { }
}
}