#region License Information /* HeuristicLab * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using HeuristicLab.PluginInfrastructure.Manager; namespace HeuristicLab.PluginInfrastructure { /// /// The SandboxApplicationManager provides properties to retrieve the list of available plugins and applications. /// It also provides methods for type discovery and instantiation for types declared in plugins. /// The SandboxApplicationManager is used in sandboxed Application Domains where permissions are restricted and /// only partially-trusted code can be executed. /// internal class WebApplicationManager : MarshalByRefObject, IApplicationManager { // private class to store plugin dependency declarations while reflecting over plugins private class PluginDependency { public string Name { get; private set; } public Version Version { get; private set; } public PluginDependency(string name, Version version) { this.Name = name; this.Version = version; } } /// /// Fired when a plugin is loaded. /// internal event EventHandler PluginLoaded; /// /// Fired when a plugin is unloaded (when the application terminates). /// internal event EventHandler PluginUnloaded; // cache for the AssemblyResolveEvent // which must be handled when assemblies are loaded dynamically after the application start protected internal Dictionary loadedAssemblies; private List loadedPlugins; private List plugins; /// /// Gets all plugins. /// public IEnumerable Plugins { get { return plugins.Cast(); } } private List applications; /// /// Gets all installed applications. /// public IEnumerable Applications { get { return applications.Cast(); } } internal WebApplicationManager() : base() { loadedAssemblies = new Dictionary(); loadedPlugins = new List(); plugins = GatherPluginDescriptions(); } static private string AssemblyDirectory { get { string codeBase = Assembly.GetExecutingAssembly().CodeBase; UriBuilder uri = new UriBuilder(codeBase); string path = Uri.UnescapeDataString(uri.Path); return Path.GetDirectoryName(path); } } // find all types implementing IPlugin in the reflectionOnlyAssemblies and create a list of plugin descriptions // the dependencies in the plugin descriptions are not yet set correctly because we need to create // the full list of all plugin descriptions first private List GatherPluginDescriptions() { /*List pluginDescriptions = new List(); // load all assemblies that are available in the project folders var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); IList loadedPaths = new List(); foreach (var asm in loadedAssemblies) { try { loadedPaths.Add(asm.Location); } catch (Exception ex) { } }*/ /*var assembliePaths = from location in loadedAssemblies select location.Location; var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray();*/ var pv = new PluginValidator() { PluginDir = AssemblyDirectory }; pv.DiscoverAndCheckPlugins(); return pv.Plugins.ToList(); /*var pluginManager = new PluginManager(AssemblyDirectory); pluginManager.DiscoverAndCheckPlugins(); return pluginManager.Plugins.ToList();*/ /*var referencedPaths = Directory.GetFiles(AssemblyDirectory, "*.dll"); var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList(); toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path)))); foreach (Assembly assembly in loadedAssemblies) { // GetExportedTypes throws FileNotFoundException when a referenced assembly // of the current assembly is missing. try { // loadedAssemblies.Add(assembly.GetName().Name, assembly); // if there is a type that implements IPlugin // use AssemblyQualifiedName to compare the types because we can't directly // compare ReflectionOnly types and execution types var assemblyPluginDescriptions = from t in assembly.GetExportedTypes() where !t.IsAbstract && t.GetInterfaces().Any(x => x.AssemblyQualifiedName == typeof(IPlugin).AssemblyQualifiedName) select GetPluginDescription(t); // TODO: Also load all plugin descriptors from assemblies liying in the app domain folder!!!!! pluginDescriptions.AddRange(assemblyPluginDescriptions); } // ignore exceptions. Just don't yield a plugin description when an exception is thrown catch (NotSupportedException) { } catch (FileNotFoundException) { } catch (FileLoadException) { } catch (InvalidPluginException) { } catch (TypeLoadException) { } catch (MissingMemberException) { } } return pluginDescriptions;*/ } /// /// Extracts plugin information for this type. /// Reads plugin name, list and type of files and dependencies of the plugin. This information is necessary for /// plugin dependency checking before plugin activation. /// /// private PluginDescription GetPluginDescription(Type pluginType) { string pluginName, pluginDescription, pluginVersion; string contactName, contactAddress; GetPluginMetaData(pluginType, out pluginName, out pluginDescription, out pluginVersion); GetPluginContactMetaData(pluginType, out contactName, out contactAddress); var pluginFiles = GetPluginFilesMetaData(pluginType); var pluginDependencies = GetPluginDependencyMetaData(pluginType); // minimal sanity check of the attribute values if (!string.IsNullOrEmpty(pluginName) && pluginFiles.Count() > 0 && // at least one file pluginFiles.Any(f => f.Type == PluginFileType.Assembly)) { // at least one assembly // create a temporary PluginDescription that contains the attribute values PluginDescription info = new PluginDescription(); info.Name = pluginName; info.Description = pluginDescription; info.Version = new Version(pluginVersion); info.ContactName = contactName; info.ContactEmail = contactAddress; info.LicenseText = ReadLicenseFiles(pluginFiles); info.AddFiles(pluginFiles); //this.pluginDependencies[info] = pluginDependencies; return info; } else { throw new InvalidPluginException("Invalid metadata in plugin " + pluginType.ToString()); } } private static string ReadLicenseFiles(IEnumerable pluginFiles) { // combine the contents of all plugin files var licenseFiles = from file in pluginFiles where file.Type == PluginFileType.License select file; if (licenseFiles.Count() == 0) return string.Empty; StringBuilder licenseTextBuilder = new StringBuilder(); licenseTextBuilder.AppendLine(File.ReadAllText(licenseFiles.First().Name)); foreach (var licenseFile in licenseFiles.Skip(1)) { licenseTextBuilder.AppendLine().AppendLine(); // leave some empty space between multiple license files licenseTextBuilder.AppendLine(File.ReadAllText(licenseFile.Name)); } return licenseTextBuilder.ToString(); } private static IEnumerable GetPluginDependencyMetaData(Type pluginType) { // get all attributes of type PluginDependency var dependencyAttributes = from attr in CustomAttributeData.GetCustomAttributes(pluginType) where IsAttributeDataForType(attr, typeof(PluginDependencyAttribute)) select attr; foreach (var dependencyAttr in dependencyAttributes) { string name = (string)dependencyAttr.ConstructorArguments[0].Value; Version version = new Version("0.0.0.0"); // default version // check if version is given for now // later when the constructor of PluginDependencyAttribute with only one argument has been removed // this conditional can be removed as well if (dependencyAttr.ConstructorArguments.Count > 1) { try { version = new Version((string)dependencyAttr.ConstructorArguments[1].Value); // might throw FormatException } catch (FormatException ex) { throw new InvalidPluginException("Invalid version format of dependency " + name + " in plugin " + pluginType.ToString(), ex); } } yield return new PluginDependency(name, version); } } private static bool IsAttributeDataForType(CustomAttributeData attributeData, Type attributeType) { return attributeData.Constructor.DeclaringType.AssemblyQualifiedName == attributeType.AssemblyQualifiedName; } private static void GetPluginContactMetaData(Type pluginType, out string contactName, out string contactAddress) { // get attribute of type ContactInformation if there is any var contactInfoAttribute = (from attr in CustomAttributeData.GetCustomAttributes(pluginType) where IsAttributeDataForType(attr, typeof(ContactInformationAttribute)) select attr).SingleOrDefault(); if (contactInfoAttribute != null) { contactName = (string)contactInfoAttribute.ConstructorArguments[0].Value; contactAddress = (string)contactInfoAttribute.ConstructorArguments[1].Value; } else { contactName = string.Empty; contactAddress = string.Empty; } } // not static because we need the PluginDir property private IEnumerable GetPluginFilesMetaData(Type pluginType) { // get all attributes of type PluginFileAttribute var pluginFileAttributes = from attr in CustomAttributeData.GetCustomAttributes(pluginType) where IsAttributeDataForType(attr, typeof(PluginFileAttribute)) select attr; foreach (var pluginFileAttribute in pluginFileAttributes) { string pluginFileName = (string)pluginFileAttribute.ConstructorArguments[0].Value; PluginFileType fileType = (PluginFileType)pluginFileAttribute.ConstructorArguments[1].Value; yield return new PluginFile(Path.GetFullPath(Path.Combine(@"C:\dev\Heuristiclab\branches\OaaS\bin", pluginFileName)), fileType); } } private static void GetPluginMetaData(Type pluginType, out string pluginName, out string pluginDescription, out string pluginVersion) { // there must be a single attribute of type PluginAttribute var pluginMetaDataAttr = (from attr in CustomAttributeData.GetCustomAttributes(pluginType) where IsAttributeDataForType(attr, typeof(PluginAttribute)) select attr).Single(); pluginName = (string)pluginMetaDataAttr.ConstructorArguments[0].Value; // default description and version pluginVersion = "0.0.0.0"; pluginDescription = string.Empty; if (pluginMetaDataAttr.ConstructorArguments.Count() == 2) { // if two arguments are given the second argument is the version pluginVersion = (string)pluginMetaDataAttr.ConstructorArguments[1].Value; } else if (pluginMetaDataAttr.ConstructorArguments.Count() == 3) { // if three arguments are given the second argument is the description and the third is the version pluginDescription = (string)pluginMetaDataAttr.ConstructorArguments[1].Value; pluginVersion = (string)pluginMetaDataAttr.ConstructorArguments[2].Value; } } /// /// Prepares the application domain for the execution of an HL application. /// Pre-loads all . /// /// Enumerable of available HL applications. /// Enumerable of plugins that should be pre-loaded. internal void PrepareApplicationDomain(IEnumerable apps, IEnumerable plugins) { this.plugins = new List(plugins); this.applications = new List(apps); ApplicationManager.RegisterApplicationManager(this); LoadPlugins(plugins); } /// /// Loads the into this application domain. /// /// Enumerable of plugins that should be loaded. private void LoadPlugins(IEnumerable plugins) { // load all loadable plugins (all dependencies available) into the execution context foreach (var desc in PluginDescriptionIterator.IterateDependenciesBottomUp(plugins.Where(x => x.PluginState != PluginState.Disabled))) { foreach (string fileName in desc.AssemblyLocations) { // load assembly reflection only first to get the full assembly name var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(fileName); // load the assembly into execution context using full assembly name var asm = Assembly.Load(reflectionOnlyAssembly.FullName); RegisterLoadedAssembly(asm); // instantiate and load all plugins in this assembly foreach (var plugin in GetInstances(asm)) { plugin.OnLoad(); loadedPlugins.Add(plugin); } } OnPluginLoaded(new PluginInfrastructureEventArgs(desc)); desc.Load(); } } /// /// Runs the application declared in . /// This is a synchronous call. When the application is terminated all plugins are unloaded. /// /// Description of the application to run internal void Run(ApplicationDescription appInfo, ICommandLineArgument[] args) { IApplication runnablePlugin = (IApplication)Activator.CreateInstance(appInfo.DeclaringAssemblyName, appInfo.DeclaringTypeName).Unwrap(); try { runnablePlugin.Run(args); } finally { // unload plugins in reverse order foreach (var plugin in loadedPlugins.Reverse()) { plugin.OnUnload(); } foreach (var desc in PluginDescriptionIterator.IterateDependenciesBottomUp(plugins.Where(x => x.PluginState != PluginState.Disabled))) { desc.Unload(); OnPluginUnloaded(new PluginInfrastructureEventArgs(desc)); } } } // register assembly in the assembly cache for the AssemblyResolveEvent private void RegisterLoadedAssembly(Assembly asm) { if (loadedAssemblies.ContainsKey(asm.FullName) || loadedAssemblies.ContainsKey(asm.GetName().Name)) { throw new ArgumentException("An assembly with the name " + asm.GetName().Name + " has been registered already.", "asm"); } loadedAssemblies.Add(asm.FullName, asm); loadedAssemblies.Add(asm.GetName().Name, asm); // add short name } /// /// Creates an instance of all types that are subtypes or the same type of the specified type and declared in /// /// Most general type. /// Enumerable of the created instances. internal static IEnumerable GetInstances(IPluginDescription plugin) where T : class { List instances = new List(); foreach (Type t in GetTypes(typeof(T), plugin, onlyInstantiable: true, includeGenericTypeDefinitions: false)) { T instance = null; try { instance = (T)Activator.CreateInstance(t); } catch { } if (instance != null) instances.Add(instance); } return instances; } /// /// Creates an instance of all types declared in assembly that are subtypes or the same type of the specified . /// /// Most general type. /// Declaring assembly. /// Enumerable of the created instances. private static IEnumerable GetInstances(Assembly asm) where T : class { List instances = new List(); foreach (Type t in GetTypes(typeof(T), asm, onlyInstantiable: true, includeGenericTypeDefinitions: false)) { T instance = null; try { instance = (T)Activator.CreateInstance(t); } catch { } if (instance != null) instances.Add(instance); } return instances; } /// /// Creates an instance of all types that are subtypes or the same type of the specified type /// /// Most general type. /// Enumerable of the created instances. internal static IEnumerable GetInstances() where T : class { return from i in GetInstances(typeof(T)) select (T)i; } /// /// Creates an instance of all types that are subtypes or the same type of the specified type /// /// Most general type. /// Enumerable of the created instances. internal static IEnumerable GetInstances(Type type) { List instances = new List(); foreach (Type t in GetTypes(type, onlyInstantiable: true, includeGenericTypeDefinitions: false)) { object instance = null; try { instance = Activator.CreateInstance(t); } catch { } if (instance != null) instances.Add(instance); } return instances; } /// /// Finds all types that are subtypes or equal to the specified type. /// /// Most general type for which to find matching types. /// Return only types that are instantiable /// Specifies if generic type definitions shall be included /// (interfaces, abstract classes... are not returned) /// Enumerable of the discovered types. internal static IEnumerable GetTypes(Type type, bool onlyInstantiable, bool includeGenericTypeDefinitions) { return from asm in AppDomain.CurrentDomain.GetAssemblies() from t in GetTypes(type, asm, onlyInstantiable, includeGenericTypeDefinitions) select t; } internal static IEnumerable GetTypes(IEnumerable types, bool onlyInstantiable, bool includeGenericTypeDefinitions, bool assignableToAllTypes) { IEnumerable result = GetTypes(types.First(), onlyInstantiable, includeGenericTypeDefinitions); foreach (Type type in types.Skip(1)) { IEnumerable discoveredTypes = GetTypes(type, onlyInstantiable, includeGenericTypeDefinitions); if (assignableToAllTypes) result = result.Intersect(discoveredTypes); else result = result.Union(discoveredTypes); } return result; } /// /// Finds all types that are subtypes or equal to the specified type if they are part of the given /// . /// /// Most general type for which to find matching types. /// The plugin the subtypes must be part of. /// Return only types that are instantiable /// Specifies if generic type definitions shall be included /// (interfaces, abstract classes... are not returned) /// Enumerable of the discovered types. internal static IEnumerable GetTypes(Type type, IPluginDescription pluginDescription, bool onlyInstantiable, bool includeGenericTypeDefinitions) { PluginDescription pluginDesc = (PluginDescription)pluginDescription; return from asm in AppDomain.CurrentDomain.GetAssemblies() where !asm.IsDynamic && !string.IsNullOrEmpty(asm.Location) where pluginDesc.AssemblyLocations.Any(location => location.Equals(Path.GetFullPath(asm.Location), StringComparison.CurrentCultureIgnoreCase)) from t in GetTypes(type, asm, onlyInstantiable, includeGenericTypeDefinitions) select t; } internal static IEnumerable GetTypes(IEnumerable types, IPluginDescription pluginDescription, bool onlyInstantiable, bool includeGenericTypeDefinitions, bool assignableToAllTypes) { IEnumerable result = GetTypes(types.First(), pluginDescription, onlyInstantiable, includeGenericTypeDefinitions); foreach (Type type in types.Skip(1)) { IEnumerable discoveredTypes = GetTypes(type, pluginDescription, onlyInstantiable, includeGenericTypeDefinitions); if (assignableToAllTypes) result = result.Intersect(discoveredTypes); else result = result.Union(discoveredTypes); } return result; } /// /// Gets types that are assignable (same of subtype) to the specified type only from the given assembly. /// /// Most general type we want to find. /// Assembly that should be searched for types. /// Return only types that are instantiable /// (interfaces, abstract classes... are not returned) /// Specifies if generic type definitions shall be included /// Enumerable of the discovered types. private static IEnumerable GetTypes(Type type, Assembly assembly, bool onlyInstantiable, bool includeGenericTypeDefinitions) { var buildTypes = from t in assembly.GetTypes() where CheckTypeCompatibility(type, t) where !IsNonDiscoverableType(t) where onlyInstantiable == false || (!t.IsAbstract && !t.IsInterface && !t.HasElementType) select BuildType(t, type); return from t in buildTypes where includeGenericTypeDefinitions || !t.IsGenericTypeDefinition select t; } private static bool IsNonDiscoverableType(Type t) { return t.GetCustomAttributes(typeof(NonDiscoverableTypeAttribute), false).Any(); } private static bool CheckTypeCompatibility(Type type, Type other) { if (type.IsAssignableFrom(other)) return true; if (type.IsGenericType && other.IsGenericType) { var otherGenericArguments = other.GetGenericArguments(); var typeGenericArguments = type.GetGenericArguments(); //check type arguments count if (otherGenericArguments.Length != typeGenericArguments.Length) return false; //check type arguments & constraints int i = 0; foreach (var genericArgument in typeGenericArguments) { if (otherGenericArguments[i].IsGenericParameter) { foreach (var constraint in otherGenericArguments[i].GetGenericParameterConstraints()) if (!constraint.IsAssignableFrom(genericArgument)) return false; } else if (genericArgument != otherGenericArguments[i]) return false; i++; } //check types try { var otherGenericTypeDefinition = other.GetGenericTypeDefinition(); if (type.IsAssignableFrom(otherGenericTypeDefinition.MakeGenericType(typeGenericArguments))) return true; } catch (Exception) { } } return false; } private static Type BuildType(Type type, Type protoType) { if (type.IsGenericType && protoType.IsGenericType) return type.GetGenericTypeDefinition().MakeGenericType(protoType.GetGenericArguments()); else return type; } private void OnPluginLoaded(PluginInfrastructureEventArgs e) { if (PluginLoaded != null) PluginLoaded(this, e); } private void OnPluginUnloaded(PluginInfrastructureEventArgs e) { if (PluginUnloaded != null) PluginUnloaded(this, e); } #region IApplicationManager Members IEnumerable IApplicationManager.GetInstances() { return GetInstances(); } IEnumerable IApplicationManager.GetInstances(Type type) { return GetInstances(type); } IEnumerable IApplicationManager.GetTypes(Type type, bool onlyInstantiable, bool includeGenericTypeDefinitions) { return GetTypes(type, onlyInstantiable, includeGenericTypeDefinitions); } IEnumerable IApplicationManager.GetTypes(IEnumerable types, bool onlyInstantiable, bool includeGenericTypeDefinitions, bool assignableToAllTypes) { return GetTypes(types, onlyInstantiable, includeGenericTypeDefinitions, assignableToAllTypes); } IEnumerable IApplicationManager.GetTypes(Type type, IPluginDescription plugin, bool onlyInstantiable, bool includeGenericTypeDefinitions) { return GetTypes(type, plugin, onlyInstantiable, includeGenericTypeDefinitions); } IEnumerable IApplicationManager.GetTypes(IEnumerable types, IPluginDescription plugin, bool onlyInstantiable, bool includeGenericTypeDefinitions, bool assignableToAllTypes) { return GetTypes(types, plugin, onlyInstantiable, includeGenericTypeDefinitions, assignableToAllTypes); } /// /// Finds the plugin that declares the type. /// /// The type of interest. /// The description of the plugin that declares the given type or null if the type has not been declared by a known plugin. public IPluginDescription GetDeclaringPlugin(Type type) { if (type == null) throw new ArgumentNullException("type"); var name = type.Assembly.GetName().Name; foreach (PluginDescription info in Plugins) { var f = Path.GetFileNameWithoutExtension(info.Files.ElementAt(0).Name); if (f.Contains(name)) return info; } return null; } #endregion } }