PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Library/Internal/VersionedPluginManager/PluginManager.cs

http://github.com/sones/sones
C# | 579 lines | 383 code | 106 blank | 90 comment | 54 complexity | ef2f6241377eae8cec50ef155386a02b MD5 | raw file
Possible License(s): AGPL-3.0, Apache-2.0, LGPL-3.0
  1. /*
  2. * sones GraphDB - Community Edition - http://www.sones.com
  3. * Copyright (C) 2007-2011 sones GmbH
  4. *
  5. * This file is part of sones GraphDB Community Edition.
  6. *
  7. * sones GraphDB is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as published by
  9. * the Free Software Foundation, version 3 of the License.
  10. *
  11. * sones GraphDB is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with sones GraphDB. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. using System;
  21. using System.Collections.Generic;
  22. using System.IO;
  23. using System.Linq;
  24. using System.Reflection;
  25. using sones.Library.ErrorHandling;
  26. using sones.Library.VersionedPluginManager.ErrorHandling;
  27. namespace sones.Library.VersionedPluginManager
  28. {
  29. public class PluginManager
  30. {
  31. #region Data
  32. #region struct ActivatorInfo
  33. /// <summary>
  34. /// Just a wrapper to hold some information about the plugin which is going to be activated.
  35. /// </summary>
  36. private struct ActivatorInfo
  37. {
  38. public Type Type { get; set; }
  39. public Version MinVersion { get; set; }
  40. public Version MaxVersion { get; set; }
  41. public Object[] CtorArgs { get; set; }
  42. public Func<Type, Object> ActivateDelegate { get; set; }
  43. }
  44. #endregion
  45. /// <summary>
  46. /// This will store the plugin inherit type and the Activator info containing the compatible version and a list of
  47. /// valid plugin instances
  48. /// </summary>
  49. private readonly Dictionary<Type, Tuple<ActivatorInfo, List<Object>>> _inheritTypeAndInstance;
  50. /// <summary>
  51. /// The locations to search for plugins
  52. /// </summary>
  53. private readonly String[] _lookupLocations;
  54. #endregion
  55. #region Events
  56. /// <summary>
  57. /// Occurs when a plugin was found and activated.
  58. /// </summary>
  59. public event PluginFoundEvent OnPluginFound;
  60. /// <summary>
  61. /// Occurs when a plugin was found but was not activated due to a incompatible version.
  62. /// </summary>
  63. public event PluginIncompatibleVersionEvent OnPluginIncompatibleVersion;
  64. #endregion
  65. #region Ctor
  66. /// <summary>
  67. /// Creates a new instance of the PluginActivator which searches at the plugin folder of the executing assembly directory for valid plugins.
  68. /// </summary>
  69. public PluginManager()
  70. {
  71. Assembly assem = Assembly.GetEntryAssembly();
  72. // ensured the execution of the tests
  73. assem = assem ?? Assembly.GetExecutingAssembly();
  74. if (assem == null)
  75. {
  76. throw new FileNotFoundException("Executing Assembly could not founded");
  77. }
  78. //notice for the refactoring
  79. // todo: recursive search into depth starting from the plugin folder
  80. string location = Path.GetDirectoryName(assem.Location);
  81. _lookupLocations = new[] {location + Path.DirectorySeparatorChar + "plugins", location};
  82. if (_lookupLocations.IsNullOrEmpty())
  83. {
  84. _lookupLocations = new[] {Environment.CurrentDirectory};
  85. }
  86. _inheritTypeAndInstance = new Dictionary<Type, Tuple<ActivatorInfo, List<object>>>();
  87. }
  88. #endregion
  89. #region Register<T1>
  90. /// <summary>
  91. /// Register the <typeparamref name="T1"/> as plugin. This can be an interface, an abstract class or
  92. /// a usual class which is a base class.
  93. /// </summary>
  94. /// <typeparam name="T1">This can be an interface, an abstract class or a usual class which is a base class.</typeparam>
  95. /// <param name="myMinVersion">The minimum allowed version.</param>
  96. /// <param name="myMaxVersion">The maximum allowed version. If null all version greater than <paramref name="myMinVersion"/> are valid.</param>
  97. /// <param name="myActivateDelegate">Using this delegate you can activate the type instance.</param>
  98. /// <param name="myCtorArgs">Optional constructor parameters which will be used at the activation time.</param>
  99. /// <returns>The same instance to register more types in a fluent way.</returns>
  100. public PluginManager Register<T1>(Version myMinVersion, Version myMaxVersion = null,
  101. Func<Type, Object> myActivateDelegate = null, params Object[] myCtorArgs)
  102. {
  103. if (_inheritTypeAndInstance.ContainsKey(typeof (T1)))
  104. {
  105. throw new Exception("Duplicate activator type '" + typeof (T1).Name + "'");
  106. }
  107. var activatorInfo = new ActivatorInfo
  108. {
  109. Type = typeof (T1),
  110. MinVersion = myMinVersion,
  111. MaxVersion = myMaxVersion,
  112. CtorArgs = myCtorArgs,
  113. ActivateDelegate = myActivateDelegate
  114. };
  115. _inheritTypeAndInstance.Add(typeof (T1),
  116. new Tuple<ActivatorInfo, List<object>>(activatorInfo, new List<object>()));
  117. return this;
  118. }
  119. /// <summary>
  120. /// Uses the AssemblyVersionCompatibilityAttribute to determine the min and max assembly version
  121. /// </summary>
  122. /// <typeparam name="T1"></typeparam>
  123. /// <param name="myCtorArgs"></param>
  124. /// <returns></returns>
  125. public PluginManager Register<T1>(params Object[] myCtorArgs)
  126. {
  127. if (_inheritTypeAndInstance.ContainsKey(typeof (T1)))
  128. {
  129. throw new Exception("Duplicate activator type '" + typeof (T1).Name + "'");
  130. }
  131. Assembly assembly = Assembly.GetCallingAssembly();
  132. object[] assemblyVersionCompatibilityAttributes =
  133. assembly.GetCustomAttributes(typeof (AssemblyVersionCompatibilityAttribute), false);
  134. AssemblyVersionCompatibilityAttribute assemblyVersionCompatibilityAttribute = null;
  135. if (assemblyVersionCompatibilityAttributes.Length > 0)
  136. {
  137. assemblyVersionCompatibilityAttribute =
  138. assemblyVersionCompatibilityAttributes.Where(
  139. avc => ((AssemblyVersionCompatibilityAttribute) avc).PluginName == typeof (T1).Name).
  140. FirstOrDefault() as AssemblyVersionCompatibilityAttribute;
  141. }
  142. if (assemblyVersionCompatibilityAttribute != null)
  143. {
  144. return Register<T1>(assemblyVersionCompatibilityAttribute.MinVersion,
  145. assemblyVersionCompatibilityAttribute.MaxVersion, null, myCtorArgs);
  146. }
  147. Version version = Assembly.GetAssembly(typeof (T1)).GetName().Version;
  148. return Register<T1>(version, version, null, myCtorArgs);
  149. }
  150. #endregion
  151. #region Discover
  152. /// <summary>
  153. /// Activate all plugins of the previously registered types.
  154. /// All newly registered types need to be activated again!
  155. /// </summary>
  156. /// <returns></returns>
  157. public void Discover(Boolean myThrowExceptionOnIncompatibleVersion = true, Boolean myPublicOnly = true)
  158. {
  159. #region Clean up old plugins
  160. foreach (var kv in _inheritTypeAndInstance)
  161. {
  162. _inheritTypeAndInstance[kv.Key].Item2.Clear();
  163. }
  164. #endregion
  165. foreach (string folder in _lookupLocations)
  166. {
  167. DiscoverPath(myThrowExceptionOnIncompatibleVersion, myPublicOnly, folder);
  168. }
  169. }
  170. private void DiscoverPath(Boolean myThrowExceptionOnIncompatibleVersion, Boolean myPublicOnly, String myPath)
  171. {
  172. #region Get all files in the _LookupLocations
  173. if (!Directory.Exists(myPath)) return;
  174. IEnumerable<string> files = Directory.EnumerateFiles(myPath, "*.dll")
  175. .Union(Directory.EnumerateFiles(myPath, "*.exe"));
  176. #endregion
  177. foreach (string file in files)
  178. {
  179. DiscoverFile(myThrowExceptionOnIncompatibleVersion, myPublicOnly, file);
  180. }
  181. }
  182. private void DiscoverFile(Boolean myThrowExceptionOnIncompatibleVersion, Boolean myPublicOnly, String myFile)
  183. {
  184. Assembly loadedPluginAssembly;
  185. #region Try to load assembly from the filename
  186. #region Load assembly
  187. try
  188. {
  189. loadedPluginAssembly = Assembly.LoadFrom(myFile);
  190. }
  191. catch (Exception e)
  192. {
  193. throw new CouldNotLoadAssemblyException(myFile, e);
  194. }
  195. #endregion
  196. #region Check all types of the assembly - this might throw a ReflectionTypeLoadException if the plugin definition does no longer match the plugin implementation
  197. try
  198. {
  199. if (loadedPluginAssembly.GetTypes().IsNullOrEmpty())
  200. {
  201. return;
  202. }
  203. }
  204. catch (ReflectionTypeLoadException)
  205. {
  206. #region Do we have a conflict of an plugin implementation?
  207. // Check all referenced assembly of this failed loadedPluginAssembly.GetTypes() and find all matching assemblies with
  208. // all types in _InheritTypeAndInstance
  209. //TODO: check more than only one reference depth...
  210. //var matchingAssemblies = new List<Tuple<AssemblyName, AssemblyName>>();
  211. foreach (AssemblyName assembly in loadedPluginAssembly.GetReferencedAssemblies())
  212. {
  213. IEnumerable<KeyValuePair<Type, Tuple<ActivatorInfo, List<object>>>> matchings =
  214. _inheritTypeAndInstance.Where(kv => Assembly.GetAssembly(kv.Key).GetName().Name == assembly.Name);
  215. if (matchings != null)
  216. {
  217. foreach (var matchAss in matchings)
  218. {
  219. //matchingAssemblies.Add(new Tuple<AssemblyName, AssemblyName>(Assembly.GetAssembly(matchAss.Key).GetName(), assembly));
  220. CheckVersion(myThrowExceptionOnIncompatibleVersion, loadedPluginAssembly,
  221. Assembly.GetAssembly(matchAss.Key).GetName(), assembly, matchAss.Value.Item1);
  222. }
  223. }
  224. }
  225. #endregion
  226. }
  227. #endregion
  228. #endregion
  229. #region Get all types of the assembly
  230. try
  231. {
  232. foreach (Type type in loadedPluginAssembly.GetTypes())
  233. {
  234. #region Type validation
  235. if (!type.IsClass || type.IsAbstract)
  236. {
  237. continue;
  238. }
  239. if (!type.IsPublic && myPublicOnly)
  240. {
  241. continue;
  242. }
  243. #region Skip _Accessor classes
  244. if (type.HasBaseType("Microsoft.VisualStudio.TestTools.UnitTesting.BaseShadow"))
  245. {
  246. continue;
  247. }
  248. #endregion
  249. //The plugin has to implement IPluginable so that we are able to initialize/distinguish them
  250. if (!typeof(IPluginable).IsInterfaceOf(type))
  251. {
  252. continue;
  253. }
  254. //The plugin has to have an empty constructor
  255. if (type.GetConstructor(Type.EmptyTypes) == null)
  256. {
  257. continue;
  258. }
  259. #endregion
  260. FindAndActivateTypes(myThrowExceptionOnIncompatibleVersion, loadedPluginAssembly, type);
  261. }
  262. }
  263. catch
  264. {
  265. //if we can't load a dll, so we drop this
  266. }
  267. #endregion
  268. }
  269. private static Type DeGenerification(Type mySearchType, Type myGenericType)
  270. {
  271. if (mySearchType.GetGenericArguments().Length > 0)
  272. {
  273. if (myGenericType.ContainsGenericParameters)
  274. {
  275. try
  276. {
  277. var generics = mySearchType.GetGenericArguments();
  278. return myGenericType.MakeGenericType(generics);
  279. }
  280. catch
  281. {
  282. }
  283. }
  284. }
  285. return myGenericType;
  286. }
  287. /// <summary>
  288. /// Will seach all registered type whether it is an plugin definition of <paramref name="myCurrentPluginType"/>.
  289. /// </summary>
  290. /// <param name="myThrowExceptionOnIncompatibleVersion">Truth value of throw an exception</param>
  291. /// <param name="myLoadedPluginAssembly">The assembly from which the <paramref name="myCurrentPluginType"/> comes from.</param>
  292. /// <param name="myCurrentPluginType">The current plugin (or not).</param>
  293. private void FindAndActivateTypes(bool myThrowExceptionOnIncompatibleVersion, Assembly myLoadedPluginAssembly,
  294. Type myCurrentPluginType)
  295. {
  296. IEnumerable<KeyValuePair<Type, Tuple<ActivatorInfo, List<object>>>> validBaseTypes =
  297. _inheritTypeAndInstance.Where(kv =>
  298. {
  299. Type realType = DeGenerification(kv.Key, myCurrentPluginType);
  300. return kv.Key.IsBaseType(realType) || kv.Key.IsInterfaceOf(realType);
  301. }
  302. );
  303. #region Take each baseType which is valid (either base or interface) and verify version and add
  304. foreach (var baseType in validBaseTypes)
  305. {
  306. ActivatorInfo activatorInfo = _inheritTypeAndInstance[baseType.Key].Item1;
  307. #region Get baseTypeAssembly and plugin referenced assembly
  308. AssemblyName baseTypeAssembly = Assembly.GetAssembly(baseType.Key).GetName();
  309. AssemblyName pluginReferencedAssembly = myLoadedPluginAssembly.GetReferencedAssembly(baseTypeAssembly.Name);
  310. #endregion
  311. Boolean _validVersion = false;
  312. try
  313. {
  314. if (CheckVersion(myThrowExceptionOnIncompatibleVersion, myLoadedPluginAssembly, baseTypeAssembly, pluginReferencedAssembly, activatorInfo))
  315. _validVersion = true;
  316. }
  317. catch (Exception)
  318. {
  319. continue;
  320. }
  321. #region Create instance and add to lookup dict
  322. if (_validVersion)
  323. {
  324. try
  325. {
  326. Object instance;
  327. Type realType = DeGenerification(baseType.Key, myCurrentPluginType);
  328. if (activatorInfo.ActivateDelegate != null)
  329. {
  330. instance = activatorInfo.ActivateDelegate(realType);
  331. }
  332. else
  333. {
  334. instance = Activator.CreateInstance(realType, activatorInfo.CtorArgs);
  335. }
  336. if (instance != null)
  337. {
  338. _inheritTypeAndInstance[baseType.Key].Item2.Add(instance);
  339. if (OnPluginFound != null)
  340. {
  341. OnPluginFound(this, new PluginFoundEventArgs(myCurrentPluginType, instance));
  342. }
  343. }
  344. }
  345. catch (Exception e)
  346. {
  347. throw new UnknownException(e);
  348. }
  349. }
  350. #endregion
  351. }
  352. #endregion
  353. }
  354. private Boolean CheckVersion(bool myThrowExceptionOnIncompatibleVersion, Assembly myPluginAssembly,
  355. AssemblyName myBaseTypeAssembly, AssemblyName myPluginReferencedAssembly,
  356. ActivatorInfo myActivatorInfo)
  357. {
  358. Boolean _validVersion = false;
  359. #region Check version
  360. //if (myBaseTypeAssembly.Version != myPluginReferencedAssembly.Version)
  361. //Console.WriteLine("Assembly version does not match! Expected '{0}' but current is '{1}'", myLoadedPluginAssembly.GetName().Version, pluginReferencedAssembly.Version);
  362. if (myActivatorInfo.MaxVersion != null)
  363. {
  364. #region Compare min and max version
  365. if (myPluginReferencedAssembly.Version.CompareTo(myActivatorInfo.MinVersion) < 0
  366. || myPluginReferencedAssembly.Version.CompareTo(myActivatorInfo.MaxVersion) > 0)
  367. {
  368. _validVersion = false;
  369. if (OnPluginIncompatibleVersion != null)
  370. {
  371. OnPluginIncompatibleVersion(this,
  372. new PluginIncompatibleVersionEventArgs(myPluginAssembly,
  373. myPluginReferencedAssembly
  374. .Version,
  375. myActivatorInfo.
  376. MinVersion,
  377. myActivatorInfo.
  378. MaxVersion,
  379. myActivatorInfo.Type));
  380. }
  381. if (myThrowExceptionOnIncompatibleVersion)
  382. {
  383. throw new IncompatiblePluginVersionException(myPluginAssembly,
  384. myPluginReferencedAssembly.Version,
  385. myActivatorInfo.MinVersion,
  386. myActivatorInfo.MaxVersion);
  387. }
  388. }
  389. else
  390. {
  391. _validVersion = true;
  392. }
  393. #endregion
  394. }
  395. else
  396. {
  397. #region Compare min version
  398. if (myPluginReferencedAssembly.Version.CompareTo(myActivatorInfo.MinVersion) < 0)
  399. {
  400. _validVersion = false;
  401. if (OnPluginIncompatibleVersion != null)
  402. {
  403. OnPluginIncompatibleVersion(this,
  404. new PluginIncompatibleVersionEventArgs(myPluginAssembly,
  405. myPluginReferencedAssembly
  406. .Version,
  407. myActivatorInfo.
  408. MinVersion,
  409. myActivatorInfo.
  410. MaxVersion,
  411. myActivatorInfo.Type));
  412. }
  413. if (myThrowExceptionOnIncompatibleVersion)
  414. {
  415. throw new IncompatiblePluginVersionException(myPluginAssembly,
  416. myPluginReferencedAssembly.Version,
  417. myActivatorInfo.MinVersion);
  418. }
  419. }
  420. else
  421. {
  422. _validVersion = true;
  423. }
  424. #endregion
  425. }
  426. #endregion
  427. return _validVersion;
  428. }
  429. #endregion
  430. #region GetPlugins
  431. /// <summary>
  432. /// Get all plugins of type <typeparamref name="T1"/>.
  433. /// </summary>
  434. /// <typeparam name="T1">The type of the plugin.</typeparam>
  435. /// <param name="mySelector">An optional selector to narrow down the result.</param>
  436. /// <returns>The plugins.</returns>
  437. public IEnumerable<T1> GetPlugins<T1>(Func<T1, Boolean> mySelector = null)
  438. {
  439. if (_inheritTypeAndInstance.ContainsKey(typeof (T1)))
  440. {
  441. foreach (object instance in _inheritTypeAndInstance[typeof (T1)].Item2)
  442. {
  443. if (mySelector == null || (mySelector != null && mySelector((T1) instance)))
  444. {
  445. yield return (T1) instance;
  446. }
  447. }
  448. }
  449. yield break;
  450. }
  451. #endregion
  452. #region HasPlugins
  453. /// <summary>
  454. /// Returns true if there are any plugins of type <typeparamref name="T1"/>.
  455. /// </summary>
  456. /// <typeparam name="T1">The type of the plugins.</typeparam>
  457. /// <param name="mySelector">An optional selector to narrow down the plugins.</param>
  458. /// <returns>True if any plugin exists.</returns>
  459. public Boolean HasPlugins<T1>(Func<T1, Boolean> mySelector = null)
  460. {
  461. if (!_inheritTypeAndInstance.ContainsKey(typeof (T1)))
  462. {
  463. return false;
  464. }
  465. if (mySelector == null)
  466. {
  467. return !_inheritTypeAndInstance[typeof (T1)].Item2.IsNullOrEmpty();
  468. }
  469. return _inheritTypeAndInstance[typeof (T1)].Item2.Any(o => mySelector((T1) o));
  470. }
  471. #endregion
  472. }
  473. }