using System; using System.Collections.Generic; using System.Reflection; using System.IO; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.ReflectionModel; using System.Linq; using DTS.Common.Core.Config; using DTS.Common.Utilities.Logging; // ReSharper disable RedundantJumpStatement // ReSharper disable UnusedTypeParameter // ReSharper disable UnusedMember.Local // ReSharper disable UnusedParameter.Local namespace DTS.Common.Core.PluginLib { /// /// PluginManager class to manage MEF plugins /// NOTE: This class has to be thread safe /// public class PluginManager { // reference to singleton plugin manager private static PluginManager _pluginManager; /// /// MEF catalog /// public AggregateCatalog PluginCatalog { get; set; } /// /// MEF container /// private CompositionContainer PluginContainer { get; set; } /// /// Thread lock /// private static readonly object THREAD_LOCK = new object(); /// /// Constructor that intializes catalog and container /// private PluginManager(string appPath) { // Create MEF catalog PluginCatalog = new AggregateCatalog(); DTSConfig.DTSConfigInit(appPath); // Get plugin folder from App.Config; var pcsh = (PluginConfigSectionHandler)DTSConfig.GetSection("DTS.Common.Core.PluginLib.Config"); //DTS.Common.Core.PluginLib.Config if (pcsh == null) { APILogger.Log("## Unable to retrieve plugin config data from DTS.Config (DTS.Common.Core.PluginLib.Config)"); throw new Exception("Unable to retrieve plugin config data from DTS.config (DTS.Common.Core.PluginLib.Config)"); } foreach (FilterHashElement element in pcsh.HashKeys) { try { var info = new DirectoryInfo(element.Value); if (!info.Exists) { APILogger.Log($"## Plugin directory does not exist: {element.Value}"); throw new IOException($"Plugin directory does not exist: {element.Value}"); } PluginCatalog.Catalogs.Add(new DirectoryCatalog(element.Value)); } catch (Exception ex) { APILogger.Log(ex); throw; } } // Create MEF container PluginContainer = new CompositionContainer(PluginCatalog); foreach (var catalog in PluginCatalog.Catalogs) { var directoryCatalog = catalog as DirectoryCatalog; if (directoryCatalog == null) { APILogger.Log("## directory catalog is null"); continue; } var pluginDir = directoryCatalog.FullPath; var files = new DirectoryInfo(pluginDir).GetFiles("*.dll"); foreach (var assembly in files.Where(f => !f.Name.StartsWith("DTS.Common") && !f.Name.StartsWith("C1") && !f.Name.StartsWith("Xceed"))) { var assemblyName = pluginDir + @"\" + assembly.Name; //var assemblyName = $"{pluginDir}{assembly.Name}"; // verift assembly exists and matches requested paramaters if (!File.Exists(assemblyName)) { APILogger.Log($"## File Not Found!: {assemblyName}"); continue; } //var info = AssemblyName.GetAssemblyName(assemblyName); // NOTE: sometimes args.Name only has short assembly name, sometimes full name with public key and version //if (info == null || (info.FullName != args.Name && (info.Name != args.Name))) //{ // continue; //} // Load assembly try { var asm = Assembly.LoadFrom(assemblyName); } catch (Exception ex) { APILogger.Log("## Failed to load assembly:", ex); throw; } } } } public List GetPluginList() where T : class { var assemblyList = new List(); var manager = GetPluginManager(string.Empty); foreach (var catalog in manager.PluginCatalog.Catalogs) { var directoryCatalog = catalog as DirectoryCatalog; if (directoryCatalog == null) continue; assemblyList.AddRange(directoryCatalog.Parts.Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly).Distinct().ToList()); } return assemblyList; } /// /// Returns MEF plugin of type t /// Throws error if no type exported by plugin or more than one plugin has exported type /// /// type to get /// reference to type t from MEF plugin public static T GetPlugin() where T : class { var export = GetPluginManager(string.Empty).PluginContainer.GetExport(); return export?.Value; } /// /// Returns MEF plugin of type T from a collection of plugins that export the same type /// /// exported type implemented by potentially multiple plugins /// string that tells us which specific plugin we want /// type T public static T GetPlugin(string configPluginSetting) where T : class { var result = new Lazy(); var manager = GetPluginManager(configPluginSetting); var plugins = manager.PluginContainer.GetExports(); //loop through plugins returned and get the specific one we're looking for foreach (var item in plugins) { if (item.Value.ToString() != configPluginSetting) continue; result = item; break; } return result.Value; } /// /// Returns list of MEF plugins that export type t /// /// type to get /// list of references to type t from MEF plugins public static IEnumerable> GetPlugins() where T : class { var manager = GetPluginManager(string.Empty); return manager.PluginContainer.GetExports(); } /// /// Returns static singleton reference to plugin manager class /// /// plugin class public static PluginManager GetPluginManager(string appPath) { if (!string.IsNullOrWhiteSpace(appPath)) { var directoryInfo = new DirectoryInfo(appPath); } lock (THREAD_LOCK) { if (_pluginManager != null) { foreach (var catalog in _pluginManager.PluginCatalog.Catalogs) { var directoryCatalog = catalog as DirectoryCatalog; if (directoryCatalog == null) continue; } } return _pluginManager ?? (_pluginManager = new PluginManager(appPath)); } } /// /// This event triggered if system can't find plugin dependancies /// CurrentDomain_AssemblyResolve can search through list of directories instead of just one /// /// /// /// private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { foreach (var catalog in PluginCatalog.Catalogs) { var directoryCatalog = catalog as DirectoryCatalog; if (directoryCatalog == null) { APILogger.Log("## null directoryCatalog"); continue; } var pluginDir = directoryCatalog.FullPath; // get name of assembly var baseName = args.Name.Split(new[] { ',' })[0]; var assemblyName = $"{pluginDir}\\{baseName}.dll"; // verift assembly exists and matches requested paramaters if (!File.Exists(assemblyName)) { APILogger.Log($"## assembly does not exist: {assemblyName}"); continue; } var info = AssemblyName.GetAssemblyName(assemblyName); // NOTE: sometimes args.Name only has short assembly name, sometimes full name with public key and version if (info == null || info.FullName != args.Name && info.Name != args.Name) { APILogger.Log("## assembly info is incomplete"); continue; } // Load assembly try { var asm = Assembly.LoadFrom(assemblyName); return asm; } catch (Exception ex) { APILogger.Log("## failed to load", ex); throw; } } return null; } } }