#region License Information
/* HeuristicLab
* Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
namespace HeuristicLab.PluginInfrastructure {
internal class Loader : MarshalByRefObject {
///
/// Event handler for loaded plugins.
///
/// The plugin that has been loaded.
public delegate void PluginLoadedEventHandler(string pluginName);
public delegate void PluginLoadFailedEventHandler(string pluginName, string args);
private Dictionary> pluginDependencies = new Dictionary>();
private List preloadedPluginInfos = new List();
private Dictionary pluginInfos = new Dictionary();
private Dictionary allPlugins = new Dictionary();
private List disabledPlugins = new List();
private string pluginDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.Properties.Settings.Default.PluginDir;
internal event PluginLoadFailedEventHandler MissingPluginFile;
internal event PluginManagerActionEventHandler PluginAction;
internal ICollection ActivePlugins {
get {
List list = new List();
foreach (PluginInfo info in allPlugins.Keys) {
if (!disabledPlugins.Exists(delegate(PluginInfo disabledInfo) { return info.Name == disabledInfo.Name; })) {
list.Add(info);
}
}
return list;
}
}
internal ICollection InstalledPlugins {
get {
return new List(allPlugins.Keys);
}
}
internal ICollection DisabledPlugins {
get {
return disabledPlugins;
}
}
private ICollection applications;
internal ICollection InstalledApplications {
get {
return applications;
}
}
private IPlugin FindPlugin(PluginInfo plugin) {
if (allPlugins.ContainsKey(plugin)) {
return allPlugins[plugin];
} else return null;
}
///
/// Init first clears all internal datastructures (including plugin lists)
/// 1. All assemblies in the plugins directory are loaded into the reflection only context.
/// 2. The loader checks if all dependencies for each assembly are available.
/// 3. All assemblies for which there are no dependencies missing are loaded into the execution context.
/// 4. Each loaded assembly is searched for a type that implements IPlugin, then one instance of each IPlugin type is activated
/// 5. The loader checks if all necessary files for each plugin are available.
/// 6. The loader builds an acyclic graph of PluginDescriptions (childs are dependencies of a plugin) based on the
/// list of assemblies of an plugin and the list of dependencies for each of those assemblies
///
/// Thrown when the file could not be loaded.
internal void Init() {
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) {
try {
return Assembly.ReflectionOnlyLoad(args.Name);
}
catch (FileLoadException ex) {
return null;
}
};
allPlugins.Clear();
disabledPlugins.Clear();
pluginInfos.Clear();
pluginsByName.Clear();
pluginDependencies.Clear();
List assemblies = ReflectionOnlyLoadDlls();
CheckAssemblyDependencies(assemblies);
CheckPluginFiles();
CheckPluginDependencies();
LoadPlugins();
DiscoveryService service = new DiscoveryService();
IApplication[] apps = service.GetInstances();
applications = new List();
foreach (IApplication application in apps) {
ApplicationInfo info = new ApplicationInfo();
info.Name = application.Name;
info.Version = application.Version;
info.Description = application.Description;
info.AutoRestart = application.AutoRestart;
info.PluginAssembly = application.GetType().Assembly.GetName().Name;
info.PluginType = application.GetType().Namespace + "." + application.GetType().Name;
applications.Add(info);
}
}
private List ReflectionOnlyLoadDlls() {
List assemblies = new List();
// load all installed plugins into the reflection only context
foreach (String filename in Directory.GetFiles(pluginDir, "*.dll")) {
try {
assemblies.Add(ReflectionOnlyLoadDll(filename));
}
catch (BadImageFormatException) { } // just ignore the case that the .dll file is not actually a CLR dll
}
return assemblies;
}
private Assembly ReflectionOnlyLoadDll(string filename) {
return Assembly.ReflectionOnlyLoadFrom(filename);
}
private void CheckAssemblyDependencies(List assemblies) {
foreach (Assembly assembly in assemblies) {
// GetExportedTypes throws FileNotFoundException when a referenced assembly
// of the current assembly is missing.
try {
Type[] exported = assembly.GetExportedTypes();
foreach (Type t in exported) {
// if there is a type that implements IPlugin
if (! t.IsAbstract && Array.Exists(t.GetInterfaces(), delegate(Type iface) {
// use AssemblyQualifiedName to compare the types because we can't directly
// compare ReflectionOnly types and Execution types
return iface.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName;
})) {
// fetch the attributes of the IPlugin type
GetPluginAttributeData(t);
}
}
}
catch (FileNotFoundException ex) {
PluginInfo info = new PluginInfo();
AssemblyName name = assembly.GetName();
info.Name = name.Name;
info.Version = name.Version;
info.Assemblies.Add(assembly.FullName);
info.Files.Add(assembly.Location);
info.Message = "File not found: " + ex.FileName;
disabledPlugins.Add(info);
}
catch (FileLoadException ex) {
PluginInfo info = new PluginInfo();
AssemblyName name = assembly.GetName();
info.Name = name.Name;
info.Version = name.Version;
info.Files.Add(assembly.Location);
info.Assemblies.Add(assembly.FullName);
info.Message = "Couldn't load file: " + ex.FileName;
disabledPlugins.Add(info);
}
catch (InvalidPluginException ex) {
PluginInfo info = new PluginInfo();
AssemblyName name = assembly.GetName();
info.Name = name.Name;
info.Version = name.Version;
info.Files.Add(assembly.Location);
info.Assemblies.Add(assembly.FullName);
info.Message = "Couldn't load plugin class from assembly: " + assembly.GetName().Name+". Necessary plugin attributes are missing.";
disabledPlugins.Add(info);
}
}
}
///
/// Extracts plugin information for this type.
/// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for
/// plugin dependency checking before plugin activation.
///
///
private void GetPluginAttributeData(Type t) {
// get all attributes of that type
IList attributes = CustomAttributeData.GetCustomAttributes(t);
List pluginAssemblies = new List();
List pluginDependencies = new List();
List pluginFiles = new List();
string pluginName = "";
// iterate through all custom attributes and search for named arguments that we are interested in
foreach (CustomAttributeData attributeData in attributes) {
List namedArguments = new List(attributeData.NamedArguments);
// if the current attribute contains a named argument with the name "Name" then extract the plugin name
CustomAttributeNamedArgument pluginNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
return arg.MemberInfo.Name == "Name";
});
if (pluginNameArgument.MemberInfo != null) {
pluginName = (string)pluginNameArgument.TypedValue.Value;
}
// if the current attribute contains a named argument with the name "Dependency" then extract the dependency
// and store it in the list of all dependencies
CustomAttributeNamedArgument dependencyNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
return arg.MemberInfo.Name == "Dependency";
});
if (dependencyNameArgument.MemberInfo != null) {
pluginDependencies.Add((string)dependencyNameArgument.TypedValue.Value);
}
// if the current attribute has a named argument "Filename" then find if the argument "Filetype" is also supplied
// and if the filetype is Assembly then store the name of the assembly in the list of assemblies
CustomAttributeNamedArgument filenameArg = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
return arg.MemberInfo.Name == "Filename";
});
CustomAttributeNamedArgument filetypeArg = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
return arg.MemberInfo.Name == "Filetype";
});
if (filenameArg.MemberInfo != null && filetypeArg.MemberInfo != null) {
pluginFiles.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
if ((PluginFileType)filetypeArg.TypedValue.Value == PluginFileType.Assembly) {
pluginAssemblies.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
}
}
}
// minimal sanity check of the attribute values
if (pluginName != "" && pluginAssemblies.Count > 0) {
// create a temporary PluginInfo that contains the attribute values
PluginInfo info = new PluginInfo();
info.Name = pluginName;
info.Version = t.Assembly.GetName().Version;
info.Assemblies = pluginAssemblies;
info.Files.AddRange(pluginFiles);
this.pluginDependencies[info] = pluginDependencies;
preloadedPluginInfos.Add(info);
} else {
throw new InvalidPluginException();
}
}
private void CheckPluginDependencies() {
foreach (PluginInfo pluginInfo in preloadedPluginInfos) {
// don't need to check plugins that are already disabled
if (disabledPlugins.Contains(pluginInfo)) {
continue;
}
visitedDependencies.Clear();
if (!CheckPluginDependencies(pluginInfo.Name)) {
PluginInfo matchingInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == pluginInfo.Name; });
if (matchingInfo == null) throw new InvalidProgramException(); // shouldn't happen
foreach (string dependency in pluginDependencies[matchingInfo]) {
PluginInfo dependencyInfo = new PluginInfo();
dependencyInfo.Name = dependency;
pluginInfo.Dependencies.Add(dependencyInfo);
}
pluginInfo.Message = "Disabled: missing plugin dependency.";
disabledPlugins.Add(pluginInfo);
}
}
}
private List visitedDependencies = new List();
private bool CheckPluginDependencies(string pluginName) {
if (!preloadedPluginInfos.Exists(delegate(PluginInfo info) { return pluginName == info.Name; }) ||
disabledPlugins.Exists(delegate(PluginInfo info) { return pluginName == info.Name; }) ||
visitedDependencies.Contains(pluginName)) {
// when the plugin is not available return false;
return false;
} else {
// otherwise check if all dependencies of the plugin are OK
// if yes then this plugin is also ok and we store it in the list of loadable plugins
PluginInfo matchingInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == pluginName; });
if (matchingInfo == null) throw new InvalidProgramException(); // shouldn't happen
foreach (string dependency in pluginDependencies[matchingInfo]) {
visitedDependencies.Add(pluginName);
if (CheckPluginDependencies(dependency) == false) {
// if only one dependency is not available that means that the current plugin also is unloadable
return false;
}
visitedDependencies.Remove(pluginName);
}
// all dependencies OK
return true;
}
}
private Dictionary pluginsByName = new Dictionary();
private void LoadPlugins() {
// load all loadable plugins (all dependencies available) into the execution context
foreach (PluginInfo pluginInfo in preloadedPluginInfos) {
if (!disabledPlugins.Contains(pluginInfo)) {
foreach (string assembly in pluginInfo.Assemblies) {
Assembly.LoadFrom(assembly);
}
}
}
Queue pluginsToLoad = new Queue();
DiscoveryService service = new DiscoveryService();
// now search and instantiate an IPlugin type in each loaded assembly
// and prepare the queue to load all plugins
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
// don't search for plugins in the PluginInfrastructure
if (assembly == this.GetType().Assembly)
continue;
Type[] availablePluginTypes = service.GetTypes(typeof(IPlugin), assembly);
foreach (Type pluginType in availablePluginTypes) {
if (!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
pluginsToLoad.Enqueue(plugin);
pluginsByName.Add(plugin.Name, plugin);
}
}
}
// load all plugins respecting their dependencies
while (pluginsToLoad.Count > 0) {
IPlugin plugin = pluginsToLoad.Dequeue();
PluginInfo pluginInfo = GetPluginInfo(plugin);
bool canLoad = true;
foreach (PluginInfo dependency in pluginInfo.Dependencies) {
if (!allPlugins.ContainsKey(dependency)) {
canLoad = false;
break;
}
}
if (canLoad) {
PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializingPlugin));
plugin.OnLoad();
allPlugins.Add(pluginInfo, plugin);
PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializedPlugin));
} else {
pluginsToLoad.Enqueue(plugin);
}
}
}
private PluginInfo GetPluginInfo(IPlugin plugin) {
if (pluginInfos.ContainsKey(plugin)) {
return pluginInfos[plugin];
}
// store the data of the plugin in a description file which can be used without loading the plugin assemblies
PluginInfo pluginInfo = new PluginInfo();
pluginInfo.Name = plugin.Name;
pluginInfo.Version = plugin.Version;
object[] customAttributes = plugin.GetType().Assembly.GetCustomAttributes(typeof(AssemblyBuildDateAttribute), false);
if (customAttributes.Length > 0) {
pluginInfo.BuildDate = ((AssemblyBuildDateAttribute)customAttributes[0]).BuildDate;
}
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
Array.ForEach(plugin.Files, delegate(string file) {
string filename = pluginDir + "/" + file;
// always use \ as the directory separator
pluginInfo.Files.Add(filename.Replace('/', '\\'));
});
PluginInfo preloadedInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == plugin.Name; });
foreach (string assembly in preloadedInfo.Assemblies) {
// always use \ as directory separator (this is necessary for discovery of types in
// plugins see DiscoveryService.GetTypes()
pluginInfo.Assemblies.Add(assembly.Replace('/', '\\'));
}
foreach (string dependency in pluginDependencies[preloadedInfo]) {
// accumulate the dependencies of each assembly into the dependencies of the whole plugin
PluginInfo dependencyInfo = GetPluginInfo(pluginsByName[dependency]);
pluginInfo.Dependencies.Add(dependencyInfo);
}
pluginInfos[plugin] = pluginInfo;
return pluginInfo;
}
private void CheckPluginFiles() {
foreach (PluginInfo plugin in preloadedPluginInfos) {
if (!CheckPluginFiles(plugin)) {
plugin.Message = "Disabled: missing plugin file.";
disabledPlugins.Add(plugin);
}
}
}
private bool CheckPluginFiles(PluginInfo pluginInfo) {
foreach (string filename in pluginInfo.Files) {
if (!File.Exists(filename)) {
if (MissingPluginFile != null) {
MissingPluginFile(pluginInfo.Name, filename);
}
return false;
}
}
return true;
}
///
/// Initializes the life time service with an infinte lease time.
///
/// null.
public override object InitializeLifetimeService() {
return null;
}
internal void OnDelete(PluginInfo pluginInfo) {
IPlugin plugin = FindPlugin(pluginInfo);
if (plugin != null) plugin.OnDelete();
}
internal void OnInstall(PluginInfo pluginInfo) {
IPlugin plugin = FindPlugin(pluginInfo);
if (plugin != null) plugin.OnInstall();
}
internal void OnPreUpdate(PluginInfo pluginInfo) {
IPlugin plugin = FindPlugin(pluginInfo);
if (plugin != null) plugin.OnPreUpdate();
}
internal void OnPostUpdate(PluginInfo pluginInfo) {
IPlugin plugin = FindPlugin(pluginInfo);
if (plugin != null) plugin.OnPostUpdate();
}
}
}