Free cookie consent management tool by TermsFeed Policy Generator

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

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