Free cookie consent management tool by TermsFeed Policy Generator

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

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

Refactored class Loader in plugin infrastructure. #799

File size: 16.1 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 PluginManagerActionEventHandler PluginAction;
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
59
60    /// <summary>
61    /// Init first clears all internal datastructures (including plugin lists)
62    /// 1. All assemblies in the plugins directory are loaded into the reflection only context.
63    /// 2. The loader checks if all necessary files for each plugin are available.
64    /// 3. The loaded builds the tree of plugin descriptions (dependencies)
65    /// 4. The loader checks if all dependencies for each plugin are ok.
66    /// 5. All plugins for which there are no dependencies missing are loaded into the execution context.
67    /// 6. Each loaded plugin (all assemblies) is searched for a types that implement IPlugin
68    ///    then one instance of each IPlugin type is activated and the OnLoad hook is called.
69    /// 7. All types implementing IApplication are discovered
70    /// </summary>
71    internal void Init() {
72      //AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) {
73      //  try {
74      //    return Assembly.ReflectionOnlyLoad(args.Name);
75      //  }
76      //  catch (FileLoadException ex) {
77      //    return null;
78      //  }
79      //};
80      pluginDependencies.Clear();
81
82      IEnumerable<Assembly> reflectionOnlyAssemblies = ReflectionOnlyLoadDlls();
83      IEnumerable<PluginDescription> pluginDescriptions = GatherPluginDescriptions(reflectionOnlyAssemblies);
84      CheckPluginFiles(pluginDescriptions);
85
86      // a full list of plugin descriptions is available now we can build the dependency tree
87      BuildDependencyTree(pluginDescriptions);
88
89      // recursively check if all necessary plugins are available and not disabled
90      // disable plugins with missing or disabled dependencies
91      CheckPluginDependencies(pluginDescriptions);
92
93      // test full loading (in contrast to reflection only loading) of plugins
94      // disables plugins that are not loaded correctly
95      LoadPlugins(pluginDescriptions);
96
97      plugins = pluginDescriptions;
98      DiscoverApplications();
99    }
100
101    private void DiscoverApplications() {
102      DiscoveryService service = new DiscoveryService();
103      IApplication[] apps = service.GetInstances<IApplication>();
104      applications = new List<ApplicationDescription>();
105
106      foreach (IApplication application in apps) {
107        ApplicationDescription info = new ApplicationDescription();
108        info.Name = application.Name;
109        info.Version = application.Version;
110        info.Description = application.Description;
111        info.AutoRestart = application.RestartOnErrors;
112        info.PluginAssembly = application.GetType().Assembly.GetName().Name;
113        info.PluginType = application.GetType().Namespace + "." + application.GetType().Name;
114
115        applications.Add(info);
116      }
117    }
118
119    private IEnumerable<Assembly> ReflectionOnlyLoadDlls() {
120      List<Assembly> assemblies = new List<Assembly>();
121      // try to load each .dll file in the plugin directory into the reflection only context
122      foreach (string filename in Directory.GetFiles(PluginDir, "*.dll")) {
123        try {
124          assemblies.Add(Assembly.ReflectionOnlyLoadFrom(filename));
125        }
126        catch (BadImageFormatException) { } // just ignore the case that the .dll file is not actually a CLR dll
127      }
128      return assemblies;
129    }
130
131    // find all types implementing IPlugin in the reflectionOnlyAssemblies and create a list of plugin descriptions
132    // the dependencies in the plugin descriptions are not yet set correctly because we need to create
133    // the full list of all plugin descriptions first
134    private IEnumerable<PluginDescription> GatherPluginDescriptions(IEnumerable<Assembly> assemblies) {
135      List<PluginDescription> pluginDescriptions = new List<PluginDescription>();
136      foreach (Assembly assembly in assemblies) {
137        // GetExportedTypes throws FileNotFoundException when a referenced assembly
138        // of the current assembly is missing.
139        try {
140          foreach (Type t in assembly.GetExportedTypes()) {
141            // if there is a type that implements IPlugin
142            // use AssemblyQualifiedName to compare the types because we can't directly
143            // compare ReflectionOnly types and Execution types
144            if (!t.IsAbstract &&
145                t.GetInterfaces().Any(x => x.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName)) {
146              // fetch the attributes of the IPlugin type
147              pluginDescriptions.Add(GetPluginDescription(t));
148            }
149          }
150        }
151        catch (FileNotFoundException ex) {
152          //PluginDescription info = new PluginDescription();
153          //AssemblyName name = assembly.GetName();
154          //info.Name = name.Name;
155          //info.Version = name.Version;
156          //info.Assemblies.Add(assembly.FullName);
157          //info.Files.Add(assembly.Location);
158          //info.Message = "File not found: " + ex.FileName;
159          //disabledPlugins.Add(info);
160        }
161        catch (FileLoadException ex) {
162          //PluginDescription info = new PluginDescription();
163          //AssemblyName name = assembly.GetName();
164          //info.Name = name.Name;
165          //info.Version = name.Version;
166          //info.Files.Add(assembly.Location);
167          //info.Assemblies.Add(assembly.FullName);
168          //info.Message = "Couldn't load file: " + ex.FileName;
169          //disabledPlugins.Add(info);
170        }
171        catch (InvalidPluginException ex) {
172          //PluginDescription info = new PluginDescription();
173          //AssemblyName name = assembly.GetName();
174          //info.Name = name.Name;
175          //info.Version = name.Version;
176          //info.Files.Add(assembly.Location);
177          //info.Assemblies.Add(assembly.FullName);
178          //info.Message = "Couldn't load plugin class from assembly: " + assembly.GetName().Name + ". Necessary plugin attributes are missing.";
179          //disabledPlugins.Add(info);
180        }
181      }
182      return pluginDescriptions;
183    }
184
185    /// <summary>
186    /// Extracts plugin information for this type.
187    /// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for
188    /// plugin dependency checking before plugin activation.
189    /// </summary>
190    /// <param name="t"></param>
191    private PluginDescription GetPluginDescription(Type pluginType) {
192      // get all attributes of that type
193      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(pluginType);
194      List<string> pluginAssemblies = new List<string>();
195      List<string> pluginDependencies = new List<string>();
196      List<string> pluginFiles = new List<string>();
197      string pluginName = null;
198      // iterate through all custom attributes and search for attributed that we are interested in
199      foreach (CustomAttributeData attributeData in attributes) {
200        if (IsAttributeDataForType(attributeData, typeof(PluginDescriptionAttribute))) {
201          pluginName = (string)attributeData.ConstructorArguments[0].Value;
202        } else if (IsAttributeDataForType(attributeData, typeof(PluginDependencyAttribute))) {
203          pluginDependencies.Add((string)attributeData.ConstructorArguments[0].Value);
204        } else if (IsAttributeDataForType(attributeData, typeof(PluginFileAttribute))) {
205          string pluginFileName = (string)attributeData.ConstructorArguments[0].Value;
206          PluginFileType fileType = (PluginFileType)attributeData.ConstructorArguments[1].Value;
207          pluginFiles.Add(PluginDir + "/" + pluginFileName);
208          if (fileType == PluginFileType.Assembly) {
209            pluginAssemblies.Add(PluginDir + "/" + pluginFileName);
210          }
211        }
212      }
213
214      // minimal sanity check of the attribute values
215      if (!string.IsNullOrEmpty(pluginName) &&
216          pluginFiles.Count > 0 &&
217          pluginAssemblies.Count > 0) {
218        // create a temporary PluginDescription that contains the attribute values
219        PluginDescription info = new PluginDescription();
220        info.Name = pluginName;
221        info.Version = pluginType.Assembly.GetName().Version;
222        info.AddAssemblies(pluginAssemblies);
223        info.AddFiles(pluginFiles);
224        info.PluginState = PluginState.Undefined;
225
226        this.pluginDependencies[info] = pluginDependencies;
227        return info;
228      } else {
229        throw new InvalidPluginException("Invalid metadata in plugin " + pluginType.ToString());
230      }
231    }
232
233    private bool IsAttributeDataForType(CustomAttributeData attributeData, Type attributeType) {
234      return attributeData.Constructor.DeclaringType.AssemblyQualifiedName == attributeType.AssemblyQualifiedName;
235    }
236
237    // builds a dependency tree of all plugin descriptions
238    // searches matching plugin descriptions based on the list of dependency names for each plugin
239    // and sets the dependencies in the plugin descriptions
240    private void BuildDependencyTree(IEnumerable<PluginDescription> pluginDescriptions) {
241      foreach (var desc in pluginDescriptions) {
242        foreach (string pluginName in pluginDependencies[desc]) {
243          var matchingDescriptions = pluginDescriptions.Where(x => x.Name == pluginName);
244          if (matchingDescriptions.Count() > 0) {
245            desc.AddDependency(matchingDescriptions.First());
246          } else {
247            // no plugin description that matches the dependency name is available => plugin is disabled
248            desc.PluginState = PluginState.Disabled;
249            break; // stop processing more dependencies
250          }
251        }
252      }
253    }
254
255    private void CheckPluginDependencies(IEnumerable<PluginDescription> pluginDescriptions) {
256      foreach (PluginDescription pluginDescription in pluginDescriptions.Where(x => x.PluginState != PluginState.Disabled)) {
257        if (IsAnyDependencyDisabled(pluginDescription)) {
258          // PluginDescription.Message = "Disabled: missing plugin dependency.";
259          pluginDescription.PluginState = PluginState.Disabled;
260        }
261      }
262    }
263
264
265    private bool IsAnyDependencyDisabled(PluginDescription descr) {
266      if (descr.PluginState == PluginState.Disabled) return true;
267      foreach (PluginDescription dependency in descr.Dependencies) {
268        if (IsAnyDependencyDisabled(dependency)) return true;
269      }
270      return false;
271    }
272
273    private void LoadPlugins(IEnumerable<PluginDescription> pluginDescriptions) {
274      // load all loadable plugins (all dependencies available) into the execution context
275      foreach (PluginDescription desc in PluginDescriptionIterator.IterateInDependencyOrder(pluginDescriptions.Where(x => x.PluginState != PluginState.Disabled))) {
276        List<Type> types = new List<Type>();
277        foreach (string assembly in desc.Assemblies) {
278          var asm = Assembly.LoadFrom(assembly);
279          foreach (Type t in asm.GetTypes()) {
280            if (typeof(IPlugin).IsAssignableFrom(t)) {
281              types.Add(t);
282            }
283          }
284        }
285
286        foreach (Type pluginType in types) {
287          if (!pluginType.IsAbstract && !pluginType.IsInterface && !pluginType.HasElementType) {
288            IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
289            plugin.OnLoad();
290            PluginAction(this, new PluginManagerActionEventArgs(plugin.Name, PluginManagerAction.PluginLoaded));
291          }
292        }
293      }
294    }
295    //private PluginDescription GetPluginDescription(IPlugin plugin) {
296    //  if (PluginDescriptions.ContainsKey(plugin)) {
297    //    return PluginDescriptions[plugin];
298    //  }
299    //  // store the data of the plugin in a description file which can be used without loading the plugin assemblies
300    //  PluginDescription PluginDescription = new PluginDescription();
301    //  PluginDescription.Name = plugin.Name;
302    //  PluginDescription.Version = plugin.Version;
303
304    //  object[] customAttributes = plugin.GetType().Assembly.GetCustomAttributes(typeof(AssemblyBuildDateAttribute), false);
305    //  if (customAttributes.Length > 0) {
306    //    PluginDescription.BuildDate = ((AssemblyBuildDateAttribute)customAttributes[0]).BuildDate;
307    //  }
308
309    //  string baseDir = AppDomain.CurrentDomain.BaseDirectory;
310
311    //  Array.ForEach<string>(plugin.FileNames, delegate(string file) {
312    //    string filename = pluginDir + "/" + file;
313    //    // always use \ as the directory separator
314    //    PluginDescription.Files.Add(filename.Replace('/', '\\'));
315    //  });
316
317    //  PluginDescription preloadedInfo = preloadedPluginDescriptions.Find(delegate(PluginDescription info) { return info.Name == plugin.Name; });
318    //  foreach (string assembly in preloadedInfo.Assemblies) {
319    //    // always use \ as directory separator (this is necessary for discovery of types in
320    //    // plugins see DiscoveryService.GetTypes()
321    //    PluginDescription.Assemblies.Add(assembly.Replace('/', '\\'));
322    //  }
323    //  foreach (string dependency in pluginDependencies[preloadedInfo]) {
324    //    // accumulate the dependencies of each assembly into the dependencies of the whole plugin
325    //    PluginDescription dependencyInfo = GetPluginDescription(pluginsByName[dependency]);
326    //    PluginDescription.Dependencies.Add(dependencyInfo);
327    //  }
328    //  PluginDescriptions[plugin] = PluginDescription;
329    //  return PluginDescription;
330    //}
331
332    // checks if all declared plugin files are actually available and disables plugins with missing files
333    private void CheckPluginFiles(IEnumerable<PluginDescription> pluginDescriptions) {
334      foreach (PluginDescription desc in pluginDescriptions) {
335        if (!CheckPluginFiles(desc)) {
336          //plugin.Message = "Disabled: missing plugin file.";
337          desc.PluginState = PluginState.Disabled;
338        }
339      }
340    }
341
342    private bool CheckPluginFiles(PluginDescription PluginDescription) {
343      foreach (string filename in PluginDescription.Files) {
344        if (!File.Exists(filename)) {
345          //if (MissingPluginFile != null) {
346          //  MissingPluginFile(PluginDescription.Name, filename);
347          //}
348          return false;
349        }
350      }
351      return true;
352    }
353
354    /// <summary>
355    /// Initializes the life time service with an infinte lease time.
356    /// </summary>
357    /// <returns><c>null</c>.</returns>
358    public override object InitializeLifetimeService() {
359      return null;
360    }
361  }
362}
Note: See TracBrowser for help on using the repository browser.