#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 { public delegate void PluginLoadedEventHandler(string pluginName); public delegate void PluginLoadFailedEventHandler(string pluginName, string args); private Dictionary activePlugins = new Dictionary(); private Dictionary allPlugins = new Dictionary(); private Dictionary pluginInfos = new Dictionary(); private Dictionary> pluginDependencies = new Dictionary>(); private Dictionary> pluginAssemblies = new Dictionary>(); private List loadablePlugins = new List(); private string pluginDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.Properties.Settings.Default.PluginDir; internal event PluginLoadFailedEventHandler MissingPluginFile; internal event PluginManagerActionEventHandler PluginAction; internal PluginInfo[] ActivePlugins { get { PluginInfo[] plugins = new PluginInfo[activePlugins.Count]; activePlugins.Keys.CopyTo(plugins, 0); return plugins; } } internal List InstalledPlugins { get { return new List(allPlugins.Keys); } } private ApplicationInfo[] applications; internal ApplicationInfo[] InstalledApplications { get { return applications; } } private IPlugin FindPlugin(PluginInfo plugin) { return activePlugins[plugin]; } /// /// 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 /// internal void Init() { AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) { return Assembly.ReflectionOnlyLoad(args.Name); }; activePlugins.Clear(); allPlugins.Clear(); pluginInfos.Clear(); pluginsByName.Clear(); loadablePlugins.Clear(); pluginDependencies.Clear(); pluginAssemblies.Clear(); List assemblies = ReflectionOnlyLoadDlls(); CheckAssemblyDependencies(assemblies); LoadPlugins(); CheckPluginFiles(); DiscoveryService service = new DiscoveryService(); IApplication[] apps = service.GetInstances(); applications = new ApplicationInfo[apps.Length]; int i = 0; foreach(IApplication application in apps) { ApplicationInfo info = new ApplicationInfo(); info.Name = application.Name; info.Version = application.Version; info.Description = application.Description; info.PluginAssembly = application.GetType().Assembly.GetName().Name; info.PluginType = application.GetType().Namespace + "." + application.GetType().Name; applications[i++] = 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")) { assemblies.Add(ReflectionOnlyLoadDll(filename)); } 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 the type implements IPlugin if(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; })) { GetPluginAttributeData(t); } } } catch(FileNotFoundException) { // when a referenced assembly cannot be loaded then ignore this assembly in the plugin discovery // TASK: add the assembly to some kind of unloadable assemblies list // this list could be displayed to the user for diagnosis } } foreach(string pluginName in this.pluginDependencies.Keys) { allDependencies.Clear(); CheckPluginDependencies(pluginName); } } /// /// 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(); string pluginName = ""; // extract relevant parameters // 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) { if((PluginFileType)filetypeArg.TypedValue.Value == PluginFileType.Assembly) { pluginAssemblies.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value); } } } // make sure that we found reasonable values if(pluginName != "" && pluginAssemblies.Count > 0) { this.pluginDependencies[pluginName] = pluginDependencies; this.pluginAssemblies[pluginName] = pluginAssemblies; } else { throw new InvalidPluginException(); } } private List allDependencies = new List(); private bool CheckPluginDependencies(string pluginName) { // when we already checked the dependencies of this plugin earlier then just return true if(loadablePlugins.Contains(pluginName)) { return true; } else if(!pluginAssemblies.ContainsKey(pluginName) || allDependencies.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 allDependencies.Add(pluginName); foreach(string dependency in pluginDependencies[pluginName]) { if(CheckPluginDependencies(dependency) == false) { // if only one dependency is not available that means that the current plugin also is unloadable return false; } } // all dependencies OK -> add to loadable list and return true loadablePlugins.Add(pluginName); return true; } } private Dictionary pluginsByName = new Dictionary(); private void LoadPlugins() { // load all loadable plugins (all dependencies available) into the execution context foreach(string plugin in loadablePlugins) { { foreach(string assembly in pluginAssemblies[plugin]) { Assembly.LoadFrom(assembly); } } } DiscoveryService service = new DiscoveryService(); // now search and instantiate an IPlugin type in each loaded assembly 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); PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializingPlugin)); pluginsByName.Add(plugin.Name, plugin); PluginInfo pluginInfo = GetPluginInfo(plugin); allPlugins.Add(pluginInfo, plugin); PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializedPlugin)); } } } } 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; 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('/', '\\')); }); // each plugin can have multiple assemlies associated // for each assembly of the plugin find the dependencies // and get the pluginDescriptions for all dependencies foreach(string assembly in pluginAssemblies[plugin.Name]) { // 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[plugin.Name]) { // 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 allPlugins.Keys) { CheckPluginFiles(plugin); } } private bool CheckPluginFiles(PluginInfo pluginInfo) { if(activePlugins.ContainsKey(pluginInfo)) { return true; } foreach(PluginInfo dependency in pluginInfo.Dependencies) { if(!CheckPluginFiles(dependency)) { return false; } } foreach(string filename in pluginInfo.Files) { if(!File.Exists(filename)) { MissingPluginFile(pluginInfo.Name, filename); return false; } } activePlugins.Add(pluginInfo, allPlugins[pluginInfo]); return true; } // infinite lease time public override object InitializeLifetimeService() { return null; } internal void OnDelete(PluginInfo pluginInfo) { FindPlugin(pluginInfo).OnDelete(); } internal void OnInstall(PluginInfo pluginInfo) { FindPlugin(pluginInfo).OnInstall(); } internal void OnPreUpdate(PluginInfo pluginInfo) { FindPlugin(pluginInfo).OnPreUpdate(); } internal void OnPostUpdate(PluginInfo pluginInfo) { FindPlugin(pluginInfo).OnPostUpdate(); } } }