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

Last change on this file since 11 was 11, checked in by gkronber, 13 years ago

fixed #22 by moving the code that creates tree-nodes for availablePlugins into the InitPlugins method

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