Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.PluginInfrastructure/Loader.cs @ 2273

Last change on this file since 2273 was 2168, checked in by swagner, 15 years ago

Fixed loading sequence of plugins (#552)

File size: 19.3 KB
RevLine 
[2]1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.Reflection;
26using System.IO;
27using System.Diagnostics;
28using System.Windows.Forms;
29
30namespace HeuristicLab.PluginInfrastructure {
31  internal class Loader : MarshalByRefObject {
[1189]32    /// <summary>
33    /// Event handler for loaded plugins.
34    /// </summary>
35    /// <param name="pluginName">The plugin that has been loaded.</param>
[2]36    public delegate void PluginLoadedEventHandler(string pluginName);
[1395]37
[2]38    public delegate void PluginLoadFailedEventHandler(string pluginName, string args);
39
[29]40    private Dictionary<PluginInfo, List<string>> pluginDependencies = new Dictionary<PluginInfo, List<string>>();
41    private List<PluginInfo> preloadedPluginInfos = new List<PluginInfo>();
42    private Dictionary<IPlugin, PluginInfo> pluginInfos = new Dictionary<IPlugin, PluginInfo>();
[2]43    private Dictionary<PluginInfo, IPlugin> allPlugins = new Dictionary<PluginInfo, IPlugin>();
[29]44    private List<PluginInfo> disabledPlugins = new List<PluginInfo>();
[11]45    private string pluginDir = Application.StartupPath + "/" + HeuristicLab.PluginInfrastructure.Properties.Settings.Default.PluginDir;
[2]46
47    internal event PluginLoadFailedEventHandler MissingPluginFile;
48    internal event PluginManagerActionEventHandler PluginAction;
49
[29]50    internal ICollection<PluginInfo> ActivePlugins {
[2]51      get {
[29]52        List<PluginInfo> list = new List<PluginInfo>();
[1229]53        foreach (PluginInfo info in allPlugins.Keys) {
54          if (!disabledPlugins.Exists(delegate(PluginInfo disabledInfo) { return info.Name == disabledInfo.Name; })) {
[29]55            list.Add(info);
56          }
57        }
58        return list;
[2]59      }
60    }
61
[29]62    internal ICollection<PluginInfo> InstalledPlugins {
[2]63      get {
64        return new List<PluginInfo>(allPlugins.Keys);
65      }
66    }
67
[29]68    internal ICollection<PluginInfo> DisabledPlugins {
[2]69      get {
[29]70        return disabledPlugins;
71      }
72    }
73
74    private ICollection<ApplicationInfo> applications;
75    internal ICollection<ApplicationInfo> InstalledApplications {
76      get {
[2]77        return applications;
78      }
79    }
80
81    private IPlugin FindPlugin(PluginInfo plugin) {
[1229]82      if (allPlugins.ContainsKey(plugin)) {
[37]83        return allPlugins[plugin];
84      } else return null;
[2]85    }
86
87
88    /// <summary>
89    /// Init first clears all internal datastructures (including plugin lists)
90    /// 1. All assemblies in the plugins directory are loaded into the reflection only context.
91    /// 2. The loader checks if all dependencies for each assembly are available.
92    /// 3. All assemblies for which there are no dependencies missing are loaded into the execution context.
93    /// 4. Each loaded assembly is searched for a type that implements IPlugin, then one instance of each IPlugin type is activated
94    /// 5. The loader checks if all necessary files for each plugin are available.
95    /// 6. The loader builds an acyclic graph of PluginDescriptions (childs are dependencies of a plugin) based on the
96    /// list of assemblies of an plugin and the list of dependencies for each of those assemblies
97    /// </summary>
[1189]98    /// <exception cref="FileLoadException">Thrown when the file could not be loaded.</exception>
[2]99    internal void Init() {
[37]100      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) {
101        try {
102          return Assembly.ReflectionOnlyLoad(args.Name);
[1229]103        }
104        catch (FileLoadException ex) {
[37]105          return null;
106        }
[1229]107      };
[2]108      allPlugins.Clear();
[29]109      disabledPlugins.Clear();
[2]110      pluginInfos.Clear();
111      pluginsByName.Clear();
112      pluginDependencies.Clear();
113
114      List<Assembly> assemblies = ReflectionOnlyLoadDlls();
115      CheckAssemblyDependencies(assemblies);
[29]116      CheckPluginFiles();
117      CheckPluginDependencies();
[2]118      LoadPlugins();
119
120      DiscoveryService service = new DiscoveryService();
121      IApplication[] apps = service.GetInstances<IApplication>();
[29]122      applications = new List<ApplicationInfo>();
[2]123
[1229]124      foreach (IApplication application in apps) {
[2]125        ApplicationInfo info = new ApplicationInfo();
126        info.Name = application.Name;
127        info.Version = application.Version;
128        info.Description = application.Description;
[242]129        info.AutoRestart = application.AutoRestart;
[2]130        info.PluginAssembly = application.GetType().Assembly.GetName().Name;
131        info.PluginType = application.GetType().Namespace + "." + application.GetType().Name;
132
[29]133        applications.Add(info);
[2]134      }
135    }
136
137    private List<Assembly> ReflectionOnlyLoadDlls() {
138      List<Assembly> assemblies = new List<Assembly>();
139      // load all installed plugins into the reflection only context
[1229]140      foreach (String filename in Directory.GetFiles(pluginDir, "*.dll")) {
[535]141        try {
142          assemblies.Add(ReflectionOnlyLoadDll(filename));
[1229]143        }
144        catch (BadImageFormatException) { } // just ignore the case that the .dll file is not actually a CLR dll
[2]145      }
146      return assemblies;
147    }
148
149    private Assembly ReflectionOnlyLoadDll(string filename) {
150      return Assembly.ReflectionOnlyLoadFrom(filename);
151    }
152
153    private void CheckAssemblyDependencies(List<Assembly> assemblies) {
[1229]154      foreach (Assembly assembly in assemblies) {
[2]155        // GetExportedTypes throws FileNotFoundException when a referenced assembly
156        // of the current assembly is missing.
157        try {
158          Type[] exported = assembly.GetExportedTypes();
159
[1229]160          foreach (Type t in exported) {
[29]161            // if there is a type that implements IPlugin
[1897]162            if (! t.IsAbstract && Array.Exists<Type>(t.GetInterfaces(), delegate(Type iface) {
[2]163              // use AssemblyQualifiedName to compare the types because we can't directly
164              // compare ReflectionOnly types and Execution types
165              return iface.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName;
166            })) {
[29]167              // fetch the attributes of the IPlugin type
[2]168              GetPluginAttributeData(t);
169            }
170          }
[1229]171        }
172        catch (FileNotFoundException ex) {
[37]173          PluginInfo info = new PluginInfo();
174          AssemblyName name = assembly.GetName();
175          info.Name = name.Name;
176          info.Version = name.Version;
177          info.Assemblies.Add(assembly.FullName);
178          info.Files.Add(assembly.Location);
179          info.Message = "File not found: " + ex.FileName;
180          disabledPlugins.Add(info);
[1229]181        }
182        catch (FileLoadException ex) {
[37]183          PluginInfo info = new PluginInfo();
184          AssemblyName name = assembly.GetName();
185          info.Name = name.Name;
186          info.Version = name.Version;
187          info.Files.Add(assembly.Location);
188          info.Assemblies.Add(assembly.FullName);
189          info.Message = "Couldn't load file: " + ex.FileName;
190          disabledPlugins.Add(info);
[2]191        }
[1395]192        catch (InvalidPluginException ex) {
193          PluginInfo info = new PluginInfo();
194          AssemblyName name = assembly.GetName();
195          info.Name = name.Name;
196          info.Version = name.Version;
197          info.Files.Add(assembly.Location);
198          info.Assemblies.Add(assembly.FullName);
199          info.Message = "Couldn't load plugin class from assembly: " + assembly.GetName().Name+". Necessary plugin attributes are missing.";
200          disabledPlugins.Add(info);
201        }
[2]202      }
203    }
204
205    /// <summary>
206    /// Extracts plugin information for this type.
207    /// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for
208    /// plugin dependency checking before plugin activation.
209    /// </summary>
210    /// <param name="t"></param>
211    private void GetPluginAttributeData(Type t) {
212      // get all attributes of that type
213      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(t);
214      List<string> pluginAssemblies = new List<string>();
215      List<string> pluginDependencies = new List<string>();
[29]216      List<string> pluginFiles = new List<string>();
[2]217      string pluginName = "";
218      // iterate through all custom attributes and search for named arguments that we are interested in
[1229]219      foreach (CustomAttributeData attributeData in attributes) {
[2]220        List<CustomAttributeNamedArgument> namedArguments = new List<CustomAttributeNamedArgument>(attributeData.NamedArguments);
221        // if the current attribute contains a named argument with the name "Name" then extract the plugin name
222        CustomAttributeNamedArgument pluginNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
223          return arg.MemberInfo.Name == "Name";
224        });
[1229]225        if (pluginNameArgument.MemberInfo != null) {
[2]226          pluginName = (string)pluginNameArgument.TypedValue.Value;
227        }
228        // if the current attribute contains a named argument with the name "Dependency" then extract the dependency
229        // and store it in the list of all dependencies
230        CustomAttributeNamedArgument dependencyNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
231          return arg.MemberInfo.Name == "Dependency";
232        });
[1229]233        if (dependencyNameArgument.MemberInfo != null) {
[2]234          pluginDependencies.Add((string)dependencyNameArgument.TypedValue.Value);
235        }
236        // if the current attribute has a named argument "Filename" then find if the argument "Filetype" is also supplied
237        // and if the filetype is Assembly then store the name of the assembly in the list of assemblies
238        CustomAttributeNamedArgument filenameArg = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
239          return arg.MemberInfo.Name == "Filename";
240        });
241        CustomAttributeNamedArgument filetypeArg = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
242          return arg.MemberInfo.Name == "Filetype";
243        });
[1229]244        if (filenameArg.MemberInfo != null && filetypeArg.MemberInfo != null) {
[29]245          pluginFiles.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
[1229]246          if ((PluginFileType)filetypeArg.TypedValue.Value == PluginFileType.Assembly) {
[11]247            pluginAssemblies.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
[2]248          }
249        }
250      }
251
[29]252      // minimal sanity check of the attribute values
[1229]253      if (pluginName != "" && pluginAssemblies.Count > 0) {
[29]254        // create a temporary PluginInfo that contains the attribute values
255        PluginInfo info = new PluginInfo();
256        info.Name = pluginName;
[37]257        info.Version = t.Assembly.GetName().Version;
[29]258        info.Assemblies = pluginAssemblies;
259        info.Files.AddRange(pluginFiles);
260        this.pluginDependencies[info] = pluginDependencies;
261        preloadedPluginInfos.Add(info);
[2]262      } else {
[8]263        throw new InvalidPluginException();
[2]264      }
265    }
266
[29]267    private void CheckPluginDependencies() {
[1229]268      foreach (PluginInfo pluginInfo in preloadedPluginInfos) {
[29]269        // don't need to check plugins that are already disabled
[1229]270        if (disabledPlugins.Contains(pluginInfo)) {
[29]271          continue;
272        }
273        visitedDependencies.Clear();
[1229]274        if (!CheckPluginDependencies(pluginInfo.Name)) {
[37]275          PluginInfo matchingInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == pluginInfo.Name; });
[1229]276          if (matchingInfo == null) throw new InvalidProgramException(); // shouldn't happen
277          foreach (string dependency in pluginDependencies[matchingInfo]) {
[37]278            PluginInfo dependencyInfo = new PluginInfo();
279            dependencyInfo.Name = dependency;
280            pluginInfo.Dependencies.Add(dependencyInfo);
281          }
282
283          pluginInfo.Message = "Disabled: missing plugin dependency.";
[29]284          disabledPlugins.Add(pluginInfo);
285        }
286      }
287    }
288
289    private List<string> visitedDependencies = new List<string>();
[2]290    private bool CheckPluginDependencies(string pluginName) {
[1229]291      if (!preloadedPluginInfos.Exists(delegate(PluginInfo info) { return pluginName == info.Name; }) ||
[29]292        disabledPlugins.Exists(delegate(PluginInfo info) { return pluginName == info.Name; }) ||
293        visitedDependencies.Contains(pluginName)) {
[2]294        // when the plugin is not available return false;
295        return false;
296      } else {
297        // otherwise check if all dependencies of the plugin are OK
298        // if yes then this plugin is also ok and we store it in the list of loadable plugins
[29]299
300        PluginInfo matchingInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == pluginName; });
[1229]301        if (matchingInfo == null) throw new InvalidProgramException(); // shouldn't happen
302        foreach (string dependency in pluginDependencies[matchingInfo]) {
[29]303          visitedDependencies.Add(pluginName);
[1229]304          if (CheckPluginDependencies(dependency) == false) {
[2]305            // if only one dependency is not available that means that the current plugin also is unloadable
306            return false;
307          }
[29]308          visitedDependencies.Remove(pluginName);
[2]309        }
[29]310        // all dependencies OK
[2]311        return true;
312      }
313    }
314
315
316    private Dictionary<string, IPlugin> pluginsByName = new Dictionary<string, IPlugin>();
317    private void LoadPlugins() {
318      // load all loadable plugins (all dependencies available) into the execution context
[1229]319      foreach (PluginInfo pluginInfo in preloadedPluginInfos) {
320        if (!disabledPlugins.Contains(pluginInfo)) {
321          foreach (string assembly in pluginInfo.Assemblies) {
[2]322            Assembly.LoadFrom(assembly);
323          }
324        }
325      }
326
[2168]327      Queue<IPlugin> pluginsToLoad = new Queue<IPlugin>();
[2]328      DiscoveryService service = new DiscoveryService();
329      // now search and instantiate an IPlugin type in each loaded assembly
[2168]330      // and prepare the queue to load all plugins
[1229]331      foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
[2]332        // don't search for plugins in the PluginInfrastructure
[1229]333        if (assembly == this.GetType().Assembly)
[2]334          continue;
335        Type[] availablePluginTypes = service.GetTypes(typeof(IPlugin), assembly);
[1229]336        foreach (Type pluginType in availablePluginTypes) {
337          if (!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
[2]338            IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
[2168]339            pluginsToLoad.Enqueue(plugin);
[2]340            pluginsByName.Add(plugin.Name, plugin);
341          }
342        }
343      }
[29]344
[2168]345      // load all plugins respecting their dependencies
346      while (pluginsToLoad.Count > 0) {
347        IPlugin plugin = pluginsToLoad.Dequeue();
[29]348        PluginInfo pluginInfo = GetPluginInfo(plugin);
[2168]349        bool canLoad = true;
350        foreach (PluginInfo dependency in pluginInfo.Dependencies) {
351          if (!allPlugins.ContainsKey(dependency)) {
352            canLoad = false;
353            break;
354          }
355        }
356        if (canLoad) {
357          PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializingPlugin));
358          plugin.OnLoad();
359          allPlugins.Add(pluginInfo, plugin);
360          PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializedPlugin));
361        } else {
362          pluginsToLoad.Enqueue(plugin);
363        }
[29]364      }
[2]365    }
366    private PluginInfo GetPluginInfo(IPlugin plugin) {
[1229]367      if (pluginInfos.ContainsKey(plugin)) {
[2]368        return pluginInfos[plugin];
369      }
370      // store the data of the plugin in a description file which can be used without loading the plugin assemblies
371      PluginInfo pluginInfo = new PluginInfo();
372      pluginInfo.Name = plugin.Name;
373      pluginInfo.Version = plugin.Version;
[91]374
375      object[] customAttributes = plugin.GetType().Assembly.GetCustomAttributes(typeof(AssemblyBuildDateAttribute), false);
[1229]376      if (customAttributes.Length > 0) {
[91]377        pluginInfo.BuildDate = ((AssemblyBuildDateAttribute)customAttributes[0]).BuildDate;
378      }
379
[2]380      string baseDir = AppDomain.CurrentDomain.BaseDirectory;
381
382      Array.ForEach<string>(plugin.Files, delegate(string file) {
383        string filename = pluginDir + "/" + file;
384        // always use \ as the directory separator
[11]385        pluginInfo.Files.Add(filename.Replace('/', '\\'));
[2]386      });
387
[29]388      PluginInfo preloadedInfo = preloadedPluginInfos.Find(delegate(PluginInfo info) { return info.Name == plugin.Name; });
[1229]389      foreach (string assembly in preloadedInfo.Assemblies) {
[2]390        // always use \ as directory separator (this is necessary for discovery of types in
391        // plugins see DiscoveryService.GetTypes()
392        pluginInfo.Assemblies.Add(assembly.Replace('/', '\\'));
393      }
[1229]394      foreach (string dependency in pluginDependencies[preloadedInfo]) {
[2]395        // accumulate the dependencies of each assembly into the dependencies of the whole plugin
396        PluginInfo dependencyInfo = GetPluginInfo(pluginsByName[dependency]);
397        pluginInfo.Dependencies.Add(dependencyInfo);
398      }
399      pluginInfos[plugin] = pluginInfo;
400      return pluginInfo;
401    }
402
403    private void CheckPluginFiles() {
[1229]404      foreach (PluginInfo plugin in preloadedPluginInfos) {
405        if (!CheckPluginFiles(plugin)) {
[37]406          plugin.Message = "Disabled: missing plugin file.";
[29]407          disabledPlugins.Add(plugin);
408        }
[2]409      }
410    }
411
412    private bool CheckPluginFiles(PluginInfo pluginInfo) {
[1229]413      foreach (string filename in pluginInfo.Files) {
414        if (!File.Exists(filename)) {
415          if (MissingPluginFile != null) {
[29]416            MissingPluginFile(pluginInfo.Name, filename);
417          }
[2]418          return false;
419        }
420      }
421      return true;
422    }
423
[1189]424    /// <summary>
425    /// Initializes the life time service with an infinte lease time.
426    /// </summary>
427    /// <returns><c>null</c>.</returns>
[2]428    public override object InitializeLifetimeService() {
429      return null;
430    }
431
432    internal void OnDelete(PluginInfo pluginInfo) {
[37]433      IPlugin plugin = FindPlugin(pluginInfo);
[1229]434      if (plugin != null) plugin.OnDelete();
[2]435    }
436
437    internal void OnInstall(PluginInfo pluginInfo) {
[37]438      IPlugin plugin = FindPlugin(pluginInfo);
[1229]439      if (plugin != null) plugin.OnInstall();
[2]440    }
441
442    internal void OnPreUpdate(PluginInfo pluginInfo) {
[37]443      IPlugin plugin = FindPlugin(pluginInfo);
[1229]444      if (plugin != null) plugin.OnPreUpdate();
[2]445    }
446
447    internal void OnPostUpdate(PluginInfo pluginInfo) {
[37]448      IPlugin plugin = FindPlugin(pluginInfo);
[1229]449      if (plugin != null) plugin.OnPostUpdate();
[2]450    }
451  }
452}
Note: See TracBrowser for help on using the repository browser.