Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PluginInfrastructure Refactoring/HeuristicLab.PluginInfrastructure.Manager/Loader.cs @ 2489

Last change on this file since 2489 was 2489, checked in by gkronber, 15 years ago

Worked on plugin infrastructure events and the display in the SplashScreen. #799

File size: 13.5 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.Linq;
29
30
31namespace HeuristicLab.PluginInfrastructure.Manager {
32  internal class Loader : MarshalByRefObject {
33    internal event EventHandler<PluginInfrastructureEventArgs> PluginLoaded;
34
35    private Dictionary<PluginDescription, List<string>> pluginDependencies;
36
37    private List<ApplicationDescription> applications;
38    internal IEnumerable<ApplicationDescription> Applications {
39      get {
40        return applications;
41      }
42    }
43
44    private IEnumerable<PluginDescription> plugins;
45    internal IEnumerable<PluginDescription> Plugins {
46      get {
47        return plugins;
48      }
49    }
50
51    public string PluginDir { get; set; }
52
53    public Loader() {
54      this.applications = new List<ApplicationDescription>();
55      this.plugins = new List<PluginDescription>();
56      this.pluginDependencies = new Dictionary<PluginDescription, List<string>>();
57
58      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyAssemblyResolveEventHandler;
59    }
60
61    private Assembly ReflectionOnlyAssemblyResolveEventHandler(object sender, ResolveEventArgs args) {
62      //try {
63      return Assembly.ReflectionOnlyLoad(args.Name);
64      //}
65      //catch (FileLoadException ex) {
66      //  return null;
67      //}
68    }
69
70
71    /// <summary>
72    /// Init first clears all internal datastructures (including plugin lists)
73    /// 1. All assemblies in the plugins directory are loaded into the reflection only context.
74    /// 2. The loader checks if all necessary files for each plugin are available.
75    /// 3. The loaded builds the tree of plugin descriptions (dependencies)
76    /// 4. The loader checks if all dependencies for each plugin are ok.
77    /// 5. All plugins for which there are no dependencies missing are loaded into the execution context.
78    /// 6. Each loaded plugin (all assemblies) is searched for a types that implement IPlugin
79    ///    then one instance of each IPlugin type is activated and the OnLoad hook is called.
80    /// 7. All types implementing IApplication are discovered
81    /// </summary>
82    internal void Init() {
83      pluginDependencies.Clear();
84
85      IEnumerable<Assembly> reflectionOnlyAssemblies = ReflectionOnlyLoadDlls();
86      IEnumerable<PluginDescription> pluginDescriptions = GatherPluginDescriptions(reflectionOnlyAssemblies);
87      CheckPluginFiles(pluginDescriptions);
88
89      // a full list of plugin descriptions is available now we can build the dependency tree
90      BuildDependencyTree(pluginDescriptions);
91
92      // recursively check if all necessary plugins are available and not disabled
93      // disable plugins with missing or disabled dependencies
94      CheckPluginDependencies(pluginDescriptions);
95
96      // mark all plugins as enabled that were not disabled in CheckPluginFiles or CheckPluginDependencies
97      foreach (var desc in pluginDescriptions)
98        if (desc.PluginState != PluginState.Disabled)
99          desc.Enable();
100
101      // test full loading (in contrast to reflection only loading) of plugins
102      // disables plugins that are not loaded correctly
103      LoadPlugins(pluginDescriptions);
104
105      plugins = pluginDescriptions;
106      DiscoverApplications();
107    }
108
109    private void DiscoverApplications() {
110      applications = new List<ApplicationDescription>();
111
112      foreach (IApplication application in GetApplications()) {
113        ApplicationDescription info = new ApplicationDescription();
114        info.Name = application.Name;
115        info.Version = application.Version;
116        info.Description = application.Description;
117        info.AutoRestart = application.RestartOnErrors;
118        info.DeclaringAssemblyName = application.GetType().Assembly.GetName().Name;
119        info.DeclaringTypeName = application.GetType().Namespace + "." + application.GetType().Name;
120
121        applications.Add(info);
122      }
123    }
124
125    private IEnumerable<IApplication> GetApplications() {
126      return from asm in AppDomain.CurrentDomain.GetAssemblies()
127             from t in asm.GetTypes()
128             where typeof(IApplication).IsAssignableFrom(t) &&
129               !t.IsAbstract && !t.IsInterface && !t.HasElementType
130             select (IApplication)Activator.CreateInstance(t);
131    }
132
133    private IEnumerable<Assembly> ReflectionOnlyLoadDlls() {
134      List<Assembly> assemblies = new List<Assembly>();
135      // try to load each .dll file in the plugin directory into the reflection only context
136      foreach (string filename in Directory.GetFiles(PluginDir, "*.dll")) {
137        try {
138          assemblies.Add(Assembly.ReflectionOnlyLoadFrom(filename));
139        }
140        catch (BadImageFormatException) { } // just ignore the case that the .dll file is not actually a CLR dll
141      }
142      return assemblies;
143    }
144
145    // find all types implementing IPlugin in the reflectionOnlyAssemblies and create a list of plugin descriptions
146    // the dependencies in the plugin descriptions are not yet set correctly because we need to create
147    // the full list of all plugin descriptions first
148    private IEnumerable<PluginDescription> GatherPluginDescriptions(IEnumerable<Assembly> assemblies) {
149      List<PluginDescription> pluginDescriptions = new List<PluginDescription>();
150      foreach (Assembly assembly in assemblies) {
151        // GetExportedTypes throws FileNotFoundException when a referenced assembly
152        // of the current assembly is missing.
153        try {
154          foreach (Type t in assembly.GetExportedTypes()) {
155            // if there is a type that implements IPlugin
156            // use AssemblyQualifiedName to compare the types because we can't directly
157            // compare ReflectionOnly types and Execution types
158            if (!t.IsAbstract &&
159                t.GetInterfaces().Any(x => x.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName)) {
160              // fetch the attributes of the IPlugin type
161              pluginDescriptions.Add(GetPluginDescription(t));
162            }
163          }
164        }
165        catch (FileNotFoundException) {
166        }
167        catch (FileLoadException) {
168        }
169        catch (InvalidPluginException) {
170        }
171      }
172      return pluginDescriptions;
173    }
174
175    /// <summary>
176    /// Extracts plugin information for this type.
177    /// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for
178    /// plugin dependency checking before plugin activation.
179    /// </summary>
180    /// <param name="t"></param>
181    private PluginDescription GetPluginDescription(Type pluginType) {
182      // get all attributes of that type
183      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(pluginType);
184      List<string> pluginAssemblies = new List<string>();
185      List<string> pluginDependencies = new List<string>();
186      List<string> pluginFiles = new List<string>();
187      string pluginName = null;
188      // iterate through all custom attributes and search for attributed that we are interested in
189      foreach (CustomAttributeData attributeData in attributes) {
190        if (IsAttributeDataForType(attributeData, typeof(PluginAttribute))) {
191          pluginName = (string)attributeData.ConstructorArguments[0].Value;
192        } else if (IsAttributeDataForType(attributeData, typeof(PluginDependencyAttribute))) {
193          pluginDependencies.Add((string)attributeData.ConstructorArguments[0].Value);
194        } else if (IsAttributeDataForType(attributeData, typeof(PluginFileAttribute))) {
195          string pluginFileName = (string)attributeData.ConstructorArguments[0].Value;
196          PluginFileType fileType = (PluginFileType)attributeData.ConstructorArguments[1].Value;
197          pluginFiles.Add(PluginDir + "/" + pluginFileName);
198          if (fileType == PluginFileType.Assembly) {
199            pluginAssemblies.Add(PluginDir + "/" + pluginFileName);
200          }
201        }
202      }
203
204      // minimal sanity check of the attribute values
205      if (!string.IsNullOrEmpty(pluginName) &&
206          pluginFiles.Count > 0 &&
207          pluginAssemblies.Count > 0) {
208        // create a temporary PluginDescription that contains the attribute values
209        PluginDescription info = new PluginDescription();
210        info.Name = pluginName;
211        info.Version = pluginType.Assembly.GetName().Version;
212        info.AddAssemblies(pluginAssemblies);
213        info.AddFiles(pluginFiles);
214
215        this.pluginDependencies[info] = pluginDependencies;
216        return info;
217      } else {
218        throw new InvalidPluginException("Invalid metadata in plugin " + pluginType.ToString());
219      }
220    }
221
222    private bool IsAttributeDataForType(CustomAttributeData attributeData, Type attributeType) {
223      return attributeData.Constructor.DeclaringType.AssemblyQualifiedName == attributeType.AssemblyQualifiedName;
224    }
225
226    // builds a dependency tree of all plugin descriptions
227    // searches matching plugin descriptions based on the list of dependency names for each plugin
228    // and sets the dependencies in the plugin descriptions
229    private void BuildDependencyTree(IEnumerable<PluginDescription> pluginDescriptions) {
230      foreach (var desc in pluginDescriptions) {
231        foreach (string pluginName in pluginDependencies[desc]) {
232          var matchingDescriptions = pluginDescriptions.Where(x => x.Name == pluginName);
233          if (matchingDescriptions.Count() > 0) {
234            desc.AddDependency(matchingDescriptions.First());
235          } else {
236            // no plugin description that matches the dependency name is available => plugin is disabled
237            desc.Disable();
238            break; // stop processing more dependencies
239          }
240        }
241      }
242    }
243
244    private void CheckPluginDependencies(IEnumerable<PluginDescription> pluginDescriptions) {
245      foreach (PluginDescription pluginDescription in pluginDescriptions.Where(x => x.PluginState != PluginState.Disabled)) {
246        if (IsAnyDependencyDisabled(pluginDescription)) {
247          // PluginDescription.Message = "Disabled: missing plugin dependency.";
248          pluginDescription.Disable();
249        }
250      }
251    }
252
253
254    private bool IsAnyDependencyDisabled(PluginDescription descr) {
255      if (descr.PluginState == PluginState.Disabled) return true;
256      foreach (PluginDescription dependency in descr.Dependencies) {
257        if (IsAnyDependencyDisabled(dependency)) return true;
258      }
259      return false;
260    }
261
262    private void LoadPlugins(IEnumerable<PluginDescription> pluginDescriptions) {
263      // load all loadable plugins (all dependencies available) into the execution context
264      foreach (var desc in PluginDescriptionIterator.IterateInDependencyOrder(pluginDescriptions
265                                                                                .Cast<IPluginDescription>()
266                                                                                .Where(x => x.PluginState != PluginState.Disabled))) {
267        List<Type> types = new List<Type>();
268        foreach (string assembly in desc.Assemblies) {
269          var asm = Assembly.LoadFrom(assembly);
270          foreach (Type t in asm.GetTypes()) {
271            if (typeof(IPlugin).IsAssignableFrom(t)) {
272              types.Add(t);
273            }
274          }
275        }
276
277        foreach (Type pluginType in types) {
278          if (!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
279            IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
280            plugin.OnLoad();
281            FirePluginLoaded(plugin.Name);
282          }
283        }
284        desc.Load();
285      }
286    }
287
288    // checks if all declared plugin files are actually available and disables plugins with missing files
289    private void CheckPluginFiles(IEnumerable<PluginDescription> pluginDescriptions) {
290      foreach (PluginDescription desc in pluginDescriptions) {
291        if (!CheckPluginFiles(desc)) {
292          desc.Disable();
293        }
294      }
295    }
296
297    private bool CheckPluginFiles(PluginDescription PluginDescription) {
298      foreach (string filename in PluginDescription.Files) {
299        if (!File.Exists(filename)) {
300          return false;
301        }
302      }
303      return true;
304    }
305
306    private void FirePluginLoaded(string pluginName) {
307      if (PluginLoaded != null)
308        PluginLoaded(this, new PluginInfrastructureEventArgs("Plugin loaded", pluginName));
309    }
310
311    /// <summary>
312    /// Initializes the life time service with an infinte lease time.
313    /// </summary>
314    /// <returns><c>null</c>.</returns>
315    public override object InitializeLifetimeService() {
316      return null;
317    }
318  }
319}
Note: See TracBrowser for help on using the repository browser.