Free cookie consent management tool by TermsFeed Policy Generator

source: branches/CEDMA-Refactoring-Ticket419/HeuristicLab.PluginInfrastructure/Loader.cs @ 1270

Last change on this file since 1270 was 1228, checked in by gkronber, 16 years ago

Implemented #471 (OnLoad hook for plugins).

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