Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HL-3.2-MonoMigration/HeuristicLab.PluginInfrastructure/Loader.cs @ 638

Last change on this file since 638 was 638, checked in by gkronber, 14 years ago

created a branch for changes needed to run HL3 on Mono 2.0
(ticket #298)

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