Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PluginInfrastructure Refactoring/HeuristicLab.PluginInfrastructure/Manager/PluginValidator.cs @ 2527

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

Implemented changes as requested by swagner. #799

File size: 16.6 KB
RevLine 
[2]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;
[2481]28using System.Linq;
[2527]29using System.Security;
[2]30
[2481]31
32namespace HeuristicLab.PluginInfrastructure.Manager {
[2503]33  /// <summary>
34  /// Discovers all installed plugins in the plugin directory. Checks correctness of plugin meta-data and if
35  /// all plugin files are available and checks plugin dependencies.
36  /// </summary>
37  internal sealed class PluginValidator : MarshalByRefObject {
[2489]38    internal event EventHandler<PluginInfrastructureEventArgs> PluginLoaded;
[2]39
[2481]40    private Dictionary<PluginDescription, List<string>> pluginDependencies;
[2]41
[2481]42    private List<ApplicationDescription> applications;
43    internal IEnumerable<ApplicationDescription> Applications {
[2]44      get {
[2503]45        if (string.IsNullOrEmpty(PluginDir)) throw new InvalidOperationException("PluginDir is not set.");
[2497]46        if (applications == null) DiscoverAndCheckPlugins();
[2481]47        return applications;
[2]48      }
49    }
50
[2481]51    private IEnumerable<PluginDescription> plugins;
52    internal IEnumerable<PluginDescription> Plugins {
[2]53      get {
[2503]54        if (string.IsNullOrEmpty(PluginDir)) throw new InvalidOperationException("PluginDir is not set.");
[2497]55        if (plugins == null) DiscoverAndCheckPlugins();
[2481]56        return plugins;
[29]57      }
58    }
59
[2504]60    internal string PluginDir { get; set; }
[2]61
[2504]62    internal PluginValidator() {
[2481]63      this.pluginDependencies = new Dictionary<PluginDescription, List<string>>();
[2488]64
[2497]65      // ReflectionOnlyAssemblyResolveEvent must be handled because we load assemblies from the plugin path
66      // (which is not listed in the default assembly lookup locations)
[2488]67      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyAssemblyResolveEventHandler;
[2]68    }
69
[2488]70    private Assembly ReflectionOnlyAssemblyResolveEventHandler(object sender, ResolveEventArgs args) {
71      return Assembly.ReflectionOnlyLoad(args.Name);
72    }
[2]73
[2488]74
[2]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.
[2503]78    /// 2. The validator checks if all necessary files for each plugin are available.
79    /// 3. The validator builds the tree of plugin descriptions (dependencies)
80    /// 4. The validator checks if all dependencies for each plugin are ok.
[2481]81    /// 5. All plugins for which there are no dependencies missing are loaded into the execution context.
82    /// 6. Each loaded plugin (all assemblies) is searched for a types that implement IPlugin
83    ///    then one instance of each IPlugin type is activated and the OnLoad hook is called.
84    /// 7. All types implementing IApplication are discovered
[2]85    /// </summary>
[2503]86    internal void DiscoverAndCheckPlugins() {
[2]87      pluginDependencies.Clear();
88
[2527]89      IEnumerable<Assembly> reflectionOnlyAssemblies = ReflectionOnlyLoadDlls(PluginDir);
[2481]90      IEnumerable<PluginDescription> pluginDescriptions = GatherPluginDescriptions(reflectionOnlyAssemblies);
91      CheckPluginFiles(pluginDescriptions);
[2]92
[2527]93      CheckPluginAssemblies(pluginDescriptions);
94
[2481]95      // a full list of plugin descriptions is available now we can build the dependency tree
96      BuildDependencyTree(pluginDescriptions);
97
98      // recursively check if all necessary plugins are available and not disabled
99      // disable plugins with missing or disabled dependencies
100      CheckPluginDependencies(pluginDescriptions);
101
[2488]102      // mark all plugins as enabled that were not disabled in CheckPluginFiles or CheckPluginDependencies
103      foreach (var desc in pluginDescriptions)
104        if (desc.PluginState != PluginState.Disabled)
105          desc.Enable();
106
[2481]107      // test full loading (in contrast to reflection only loading) of plugins
108      // disables plugins that are not loaded correctly
109      LoadPlugins(pluginDescriptions);
110
111      plugins = pluginDescriptions;
112      DiscoverApplications();
113    }
114
115    private void DiscoverApplications() {
116      applications = new List<ApplicationDescription>();
[2]117
[2488]118      foreach (IApplication application in GetApplications()) {
[2504]119        Type appType = application.GetType();
120        ApplicationAttribute attr = (from x in appType.GetCustomAttributes(typeof(ApplicationAttribute), false)
121                                     select (ApplicationAttribute)x).Single();
[2481]122        ApplicationDescription info = new ApplicationDescription();
[2]123        info.Name = application.Name;
[2504]124        info.Version = appType.Assembly.GetName().Version;
[2]125        info.Description = application.Description;
[2504]126        info.AutoRestart = attr.RestartOnErrors;
127        info.DeclaringAssemblyName = appType.Assembly.GetName().Name;
128        info.DeclaringTypeName = appType.Namespace + "." + application.GetType().Name;
[2]129
[29]130        applications.Add(info);
[2]131      }
132    }
133
[2504]134    private static IEnumerable<IApplication> GetApplications() {
[2488]135      return from asm in AppDomain.CurrentDomain.GetAssemblies()
136             from t in asm.GetTypes()
137             where typeof(IApplication).IsAssignableFrom(t) &&
138               !t.IsAbstract && !t.IsInterface && !t.HasElementType
139             select (IApplication)Activator.CreateInstance(t);
140    }
141
[2527]142    private static IEnumerable<Assembly> ReflectionOnlyLoadDlls(string baseDir) {
[2]143      List<Assembly> assemblies = new List<Assembly>();
[2527]144      // recursively load .dll files in subdirectories
145      foreach (string dirName in Directory.GetDirectories(baseDir)) {
146        assemblies.AddRange(ReflectionOnlyLoadDlls(dirName));
147      }
[2481]148      // try to load each .dll file in the plugin directory into the reflection only context
[2527]149      foreach (string filename in Directory.GetFiles(baseDir, "*.dll")) {
[535]150        try {
[2481]151          assemblies.Add(Assembly.ReflectionOnlyLoadFrom(filename));
[1229]152        }
[2503]153        catch (BadImageFormatException) { } // just ignore the case that the .dll file is not a CLR assembly (e.g. a native dll)
[2527]154        catch (FileLoadException) { }
155        catch (SecurityException) { }
[2]156      }
157      return assemblies;
158    }
159
[2527]160    /// <summary>
161    /// Checks if all plugin assemblies can be loaded. If an assembly can't be loaded the plugin is disabled.
162    /// </summary>
163    /// <param name="pluginDescriptions"></param>
164    private void CheckPluginAssemblies(IEnumerable<PluginDescription> pluginDescriptions) {
165      foreach (var desc in pluginDescriptions.Where(x => x.PluginState != PluginState.Disabled)) {
166        try {
167          foreach (var asm in desc.Assemblies) {
168            Assembly.ReflectionOnlyLoadFrom(asm);
169          }
170        }
171        catch (BadImageFormatException) {
172          // disable the plugin
173          desc.Disable();
174        }
175        catch (FileNotFoundException) {
176          // disable the plugin
177          desc.Disable();
178        }
179        catch (FileLoadException) {
180          // disable the plugin
181          desc.Disable();
182        }
183        catch (ArgumentException) {
184          // disable the plugin
185          desc.Disable();
186        }
187        catch (SecurityException) {
188          // disable the plugin
189          desc.Disable();
190        }
191      }
192    }
193
194
[2481]195    // find all types implementing IPlugin in the reflectionOnlyAssemblies and create a list of plugin descriptions
196    // the dependencies in the plugin descriptions are not yet set correctly because we need to create
197    // the full list of all plugin descriptions first
198    private IEnumerable<PluginDescription> GatherPluginDescriptions(IEnumerable<Assembly> assemblies) {
199      List<PluginDescription> pluginDescriptions = new List<PluginDescription>();
[1229]200      foreach (Assembly assembly in assemblies) {
[2]201        // GetExportedTypes throws FileNotFoundException when a referenced assembly
202        // of the current assembly is missing.
203        try {
[2527]204          // if there is a type that implements IPlugin
205          // use AssemblyQualifiedName to compare the types because we can't directly
206          // compare ReflectionOnly types and execution types
207          var assemblyPluginDescriptions = from t in assembly.GetExportedTypes()
208                                           where !t.IsAbstract && t.GetInterfaces().Any(x => x.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName)
209                                           select GetPluginDescription(t);
210          pluginDescriptions.AddRange(assemblyPluginDescriptions);
[1229]211        }
[2497]212        // ignore exceptions. Just don't yield a plugin description when an exception is thrown
[2489]213        catch (FileNotFoundException) {
[1229]214        }
[2489]215        catch (FileLoadException) {
[2]216        }
[2489]217        catch (InvalidPluginException) {
[1395]218        }
[2]219      }
[2481]220      return pluginDescriptions;
[2]221    }
222
223    /// <summary>
224    /// Extracts plugin information for this type.
225    /// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for
226    /// plugin dependency checking before plugin activation.
227    /// </summary>
228    /// <param name="t"></param>
[2481]229    private PluginDescription GetPluginDescription(Type pluginType) {
[2]230      // get all attributes of that type
[2481]231      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(pluginType);
[2]232      List<string> pluginAssemblies = new List<string>();
233      List<string> pluginDependencies = new List<string>();
[29]234      List<string> pluginFiles = new List<string>();
[2481]235      string pluginName = null;
[2513]236      string pluginDescription = null;
[2481]237      // iterate through all custom attributes and search for attributed that we are interested in
[1229]238      foreach (CustomAttributeData attributeData in attributes) {
[2488]239        if (IsAttributeDataForType(attributeData, typeof(PluginAttribute))) {
[2481]240          pluginName = (string)attributeData.ConstructorArguments[0].Value;
[2513]241          if (attributeData.ConstructorArguments.Count() == 2) {
242            pluginDescription = (string)attributeData.ConstructorArguments[1].Value;
243          } else pluginDescription = pluginName;
[2481]244        } else if (IsAttributeDataForType(attributeData, typeof(PluginDependencyAttribute))) {
245          pluginDependencies.Add((string)attributeData.ConstructorArguments[0].Value);
246        } else if (IsAttributeDataForType(attributeData, typeof(PluginFileAttribute))) {
247          string pluginFileName = (string)attributeData.ConstructorArguments[0].Value;
248          PluginFileType fileType = (PluginFileType)attributeData.ConstructorArguments[1].Value;
[2527]249          pluginFiles.Add(Path.GetFullPath(Path.Combine(PluginDir, pluginFileName)));
[2481]250          if (fileType == PluginFileType.Assembly) {
[2527]251            pluginAssemblies.Add(Path.GetFullPath(Path.Combine(PluginDir, pluginFileName)));
[2]252          }
253        }
254      }
255
[2504]256      var buildDates = from attr in CustomAttributeData.GetCustomAttributes(pluginType.Assembly)
257                       where IsAttributeDataForType(attr, typeof(AssemblyBuildDateAttribute))
258                       select (string)attr.ConstructorArguments[0].Value;
259
[29]260      // minimal sanity check of the attribute values
[2517]261      if (!string.IsNullOrEmpty(pluginName) &&
[2481]262          pluginFiles.Count > 0 &&
[2504]263          pluginAssemblies.Count > 0 &&
264          buildDates.Count() == 1) {
[2481]265        // create a temporary PluginDescription that contains the attribute values
266        PluginDescription info = new PluginDescription();
[29]267        info.Name = pluginName;
[2513]268        info.Description = pluginDescription;
[2481]269        info.Version = pluginType.Assembly.GetName().Version;
[2504]270        info.BuildDate = DateTime.Parse(buildDates.Single(), System.Globalization.CultureInfo.InvariantCulture);
[2481]271        info.AddAssemblies(pluginAssemblies);
272        info.AddFiles(pluginFiles);
273
[29]274        this.pluginDependencies[info] = pluginDependencies;
[2481]275        return info;
[2]276      } else {
[2481]277        throw new InvalidPluginException("Invalid metadata in plugin " + pluginType.ToString());
[2]278      }
279    }
280
[2504]281    private static bool IsAttributeDataForType(CustomAttributeData attributeData, Type attributeType) {
[2481]282      return attributeData.Constructor.DeclaringType.AssemblyQualifiedName == attributeType.AssemblyQualifiedName;
283    }
284
285    // builds a dependency tree of all plugin descriptions
286    // searches matching plugin descriptions based on the list of dependency names for each plugin
287    // and sets the dependencies in the plugin descriptions
288    private void BuildDependencyTree(IEnumerable<PluginDescription> pluginDescriptions) {
289      foreach (var desc in pluginDescriptions) {
290        foreach (string pluginName in pluginDependencies[desc]) {
291          var matchingDescriptions = pluginDescriptions.Where(x => x.Name == pluginName);
292          if (matchingDescriptions.Count() > 0) {
[2517]293            desc.AddDependency(matchingDescriptions.Single());
[2481]294          } else {
295            // no plugin description that matches the dependency name is available => plugin is disabled
[2488]296            desc.Disable();
[2481]297          }
[29]298        }
[2481]299      }
300    }
[37]301
[2481]302    private void CheckPluginDependencies(IEnumerable<PluginDescription> pluginDescriptions) {
303      foreach (PluginDescription pluginDescription in pluginDescriptions.Where(x => x.PluginState != PluginState.Disabled)) {
304        if (IsAnyDependencyDisabled(pluginDescription)) {
[2488]305          pluginDescription.Disable();
[29]306        }
307      }
308    }
309
310
[2481]311    private bool IsAnyDependencyDisabled(PluginDescription descr) {
312      if (descr.PluginState == PluginState.Disabled) return true;
313      foreach (PluginDescription dependency in descr.Dependencies) {
314        if (IsAnyDependencyDisabled(dependency)) return true;
[2]315      }
[2481]316      return false;
[2]317    }
318
[2481]319    private void LoadPlugins(IEnumerable<PluginDescription> pluginDescriptions) {
[2]320      // load all loadable plugins (all dependencies available) into the execution context
[2517]321      foreach (var desc in PluginDescriptionIterator.IterateDependenciesBottomUp(pluginDescriptions
[2488]322                                                                                .Where(x => x.PluginState != PluginState.Disabled))) {
[2481]323        List<Type> types = new List<Type>();
324        foreach (string assembly in desc.Assemblies) {
325          var asm = Assembly.LoadFrom(assembly);
326          foreach (Type t in asm.GetTypes()) {
327            if (typeof(IPlugin).IsAssignableFrom(t)) {
328              types.Add(t);
329            }
[2]330          }
331        }
332
[2481]333        foreach (Type pluginType in types) {
[1229]334          if (!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
[2]335            IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
[2481]336            plugin.OnLoad();
[2503]337            OnPluginLoaded(new PluginInfrastructureEventArgs("Plugin loaded", plugin.Name));
[2]338          }
339        }
[2489]340        desc.Load();
[2]341      }
342    }
[91]343
[2481]344    // checks if all declared plugin files are actually available and disables plugins with missing files
[2527]345    private void CheckPluginFiles(IEnumerable<PluginDescription> pluginDescriptions) {
[2481]346      foreach (PluginDescription desc in pluginDescriptions) {
347        if (!CheckPluginFiles(desc)) {
[2488]348          desc.Disable();
[29]349        }
[2]350      }
351    }
352
[2527]353    private bool CheckPluginFiles(PluginDescription pluginDescription) {
[2504]354      foreach (string filename in pluginDescription.Files) {
[2527]355        if (!FileLiesInDirectory(PluginDir, filename) ||
356          !File.Exists(filename)) {
[2]357          return false;
358        }
359      }
360      return true;
361    }
362
[2527]363    private static bool FileLiesInDirectory(string dir, string fileName) {
364      var basePath = Path.GetFullPath(dir);
365      return Path.GetFullPath(fileName).StartsWith(basePath);
366    }
367
[2503]368    internal void OnPluginLoaded(PluginInfrastructureEventArgs e) {
[2489]369      if (PluginLoaded != null)
[2503]370        PluginLoaded(this, e);
[2489]371    }
372
[1189]373    /// <summary>
[2497]374    /// Initializes the life time service with an infinite lease time.
[1189]375    /// </summary>
376    /// <returns><c>null</c>.</returns>
[2]377    public override object InitializeLifetimeService() {
378      return null;
379    }
380  }
381}
Note: See TracBrowser for help on using the repository browser.