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

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

fixed #1

File size: 15.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, 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        allDependencies.Clear();
159        CheckPluginDependencies(pluginName);
160      }
161    }
162
163    /// <summary>
164    /// Extracts plugin information for this type.
165    /// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for
166    /// plugin dependency checking before plugin activation.
167    /// </summary>
168    /// <param name="t"></param>
169    private void GetPluginAttributeData(Type t) {
170      // get all attributes of that type
171      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(t);
172
173      List<string> pluginAssemblies = new List<string>();
174      List<string> pluginDependencies = new List<string>();
175      string pluginName = "";
176
177      // extract relevant parameters
178      // iterate through all custom attributes and search for named arguments that we are interested in
179      foreach(CustomAttributeData attributeData in attributes) {
180        List<CustomAttributeNamedArgument> namedArguments = new List<CustomAttributeNamedArgument>(attributeData.NamedArguments);
181
182        // if the current attribute contains a named argument with the name "Name" then extract the plugin name
183        CustomAttributeNamedArgument pluginNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
184          return arg.MemberInfo.Name == "Name";
185        });
186        if(pluginNameArgument.MemberInfo != null) {
187          pluginName = (string)pluginNameArgument.TypedValue.Value;
188        }
189
190        // if the current attribute contains a named argument with the name "Dependency" then extract the dependency
191        // and store it in the list of all dependencies
192        CustomAttributeNamedArgument dependencyNameArgument = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
193          return arg.MemberInfo.Name == "Dependency";
194        });
195
196        if(dependencyNameArgument.MemberInfo != null) {
197          pluginDependencies.Add((string)dependencyNameArgument.TypedValue.Value);
198        }
199
200        // if the current attribute has a named argument "Filename" then find if the argument "Filetype" is also supplied
201        // and if the filetype is Assembly then store the name of the assembly in the list of assemblies
202        CustomAttributeNamedArgument filenameArg = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
203          return arg.MemberInfo.Name == "Filename";
204        });
205        CustomAttributeNamedArgument filetypeArg = namedArguments.Find(delegate(CustomAttributeNamedArgument arg) {
206          return arg.MemberInfo.Name == "Filetype";
207        });
208        if(filenameArg.MemberInfo != null && filetypeArg.MemberInfo != null) {
209          if((PluginFileType)filetypeArg.TypedValue.Value == PluginFileType.Assembly) {
210            pluginAssemblies.Add(pluginDir + "/" + (string)filenameArg.TypedValue.Value);
211          }
212        }
213      }
214
215      // make sure that we found reasonable values
216      if(pluginName != "" && pluginAssemblies.Count > 0) {
217        this.pluginDependencies[pluginName] = pluginDependencies;
218        this.pluginAssemblies[pluginName] = pluginAssemblies;
219      } else {
220        throw new InvalidPluginException();
221      }
222    }
223
224    private List<string> allDependencies = new List<string>();
225    private bool CheckPluginDependencies(string pluginName) {
226      // when we already checked the dependencies of this plugin earlier then just return true
227      if(loadablePlugins.Contains(pluginName)) {
228        return true;
229      } else if(!pluginAssemblies.ContainsKey(pluginName) || allDependencies.Contains(pluginName)) {
230        // when the plugin is not available return false;
231        return false;
232      } else {
233        // otherwise check if all dependencies of the plugin are OK
234        // if yes then this plugin is also ok and we store it in the list of loadable plugins
235        allDependencies.Add(pluginName);
236        foreach(string dependency in pluginDependencies[pluginName]) {
237          if(CheckPluginDependencies(dependency) == false) {
238            // if only one dependency is not available that means that the current plugin also is unloadable
239            return false;
240          }
241        }
242        // all dependencies OK -> add to loadable list and return true
243        loadablePlugins.Add(pluginName);
244        return true;
245      }
246    }
247
248
249    private Dictionary<string, IPlugin> pluginsByName = new Dictionary<string, IPlugin>();
250
251    private void LoadPlugins() {
252      // load all loadable plugins (all dependencies available) into the execution context
253      foreach(string plugin in loadablePlugins) {
254        {
255          foreach(string assembly in pluginAssemblies[plugin]) {
256            Assembly.LoadFrom(assembly);
257          }
258        }
259      }
260
261      DiscoveryService service = new DiscoveryService();
262      // now search and instantiate an IPlugin type in each loaded assembly
263      foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
264        // don't search for plugins in the PluginInfrastructure
265        if(assembly == this.GetType().Assembly)
266          continue;
267        Type[] availablePluginTypes = service.GetTypes(typeof(IPlugin), assembly);
268
269
270        foreach(Type pluginType in availablePluginTypes) {
271          if(!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
272            IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
273            PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializingPlugin));
274
275            pluginsByName.Add(plugin.Name, plugin);
276            PluginInfo pluginInfo = GetPluginInfo(plugin);
277
278
279            allPlugins.Add(pluginInfo, plugin);
280            PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.InitializedPlugin));
281          }
282        }
283      }
284    }
285
286
287    private PluginInfo GetPluginInfo(IPlugin plugin) {
288      if(pluginInfos.ContainsKey(plugin)) {
289        return pluginInfos[plugin];
290      }
291
292      // store the data of the plugin in a description file which can be used without loading the plugin assemblies
293      PluginInfo pluginInfo = new PluginInfo();
294      pluginInfo.Name = plugin.Name;
295      pluginInfo.Version = plugin.Version;
296      string baseDir = AppDomain.CurrentDomain.BaseDirectory;
297
298      Array.ForEach<string>(plugin.Files, delegate(string file) {
299        string filename = pluginDir + "/" + file;
300        // always use \ as the directory separator
301        pluginInfo.Files.Add(filename.Replace('/', '\\'));
302      });
303
304      // each plugin can have multiple assemlies associated
305      // for each assembly of the plugin find the dependencies
306      // and get the pluginDescriptions for all dependencies
307      foreach(string assembly in pluginAssemblies[plugin.Name]) {
308        // always use \ as directory separator (this is necessary for discovery of types in
309        // plugins see DiscoveryService.GetTypes()
310        pluginInfo.Assemblies.Add(assembly.Replace('/', '\\'));
311
312      }
313      foreach(string dependency in pluginDependencies[plugin.Name]) {
314        // accumulate the dependencies of each assembly into the dependencies of the whole plugin
315        PluginInfo dependencyInfo = GetPluginInfo(pluginsByName[dependency]);
316        pluginInfo.Dependencies.Add(dependencyInfo);
317      }
318
319      pluginInfos[plugin] = pluginInfo;
320
321      return pluginInfo;
322    }
323
324    private void CheckPluginFiles() {
325      foreach(PluginInfo plugin in allPlugins.Keys) {
326        CheckPluginFiles(plugin);
327      }
328    }
329
330    private bool CheckPluginFiles(PluginInfo pluginInfo) {
331      if(activePlugins.ContainsKey(pluginInfo)) {
332        return true;
333      }
334      foreach(PluginInfo dependency in pluginInfo.Dependencies) {
335        if(!CheckPluginFiles(dependency)) {
336          return false;
337        }
338      }
339      foreach(string filename in pluginInfo.Files) {
340        if(!File.Exists(filename)) {
341          MissingPluginFile(pluginInfo.Name, filename);
342          return false;
343        }
344      }
345
346      activePlugins.Add(pluginInfo, allPlugins[pluginInfo]);
347      return true;
348    }
349
350    // infinite lease time
351    public override object InitializeLifetimeService() {
352      return null;
353    }
354
355    internal void OnDelete(PluginInfo pluginInfo) {
356      FindPlugin(pluginInfo).OnDelete();
357    }
358
359    internal void OnInstall(PluginInfo pluginInfo) {
360      FindPlugin(pluginInfo).OnInstall();
361    }
362
363    internal void OnPreUpdate(PluginInfo pluginInfo) {
364      FindPlugin(pluginInfo).OnPreUpdate();
365    }
366
367    internal void OnPostUpdate(PluginInfo pluginInfo) {
368      FindPlugin(pluginInfo).OnPostUpdate();
369    }
370  }
371}
Note: See TracBrowser for help on using the repository browser.