Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 317 was 242, checked in by gkronber, 16 years ago

added support for "service" applications that are restarted automatically when they crash with an exception. (related to #149)

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