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

/trunk/Kona.Infrastructure/Extensibility/Plugins/Plugin.cs

#
C# | 301 lines | 213 code | 39 blank | 49 comment | 49 complexity | 3a805ea5a9bd74a860b0a34673e9f5ba MD5 | raw file
Possible License(s): BSD-3-Clause
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Web.Compilation;
  5. using System.Reflection;
  6. using System.Linq;
  7. using System.Web.UI.WebControls;
  8. using System.Web.Mvc;
  9. using System.Web.UI;
  10. using System.Web;
  11. using System.IO;
  12. namespace Kona.Infrastructure {
  13. public class Plugin {
  14. // TODO: ensure thread safety.
  15. // TODO: make sure the scope of the cache here is small enough.
  16. private static List<Plugin> _plugins = new List<Plugin>();
  17. private static Hashtable _methods = Hashtable.Synchronized(new Hashtable());
  18. private static Hashtable _cached = Hashtable.Synchronized(new Hashtable());
  19. // InitializePlugins needs to be reentrant
  20. private static bool s_inited;
  21. private static object s_initLock = new object();
  22. private static IList<PluginSetting> _settings;
  23. //settings
  24. PluginSetting _setting;
  25. public PluginSetting Settings {
  26. get {
  27. _setting=Plugin._settings.SingleOrDefault(x => x.PluginName.Equals(this.PluginName, StringComparison.InvariantCultureIgnoreCase));
  28. return _setting ?? new PluginSetting();
  29. }
  30. }
  31. public bool IsEnabled {
  32. get {
  33. bool result = false;
  34. //the plugin is enabled if
  35. //1) the settings have been filled in
  36. //2) and the user says it is
  37. if (this.Settings.IsEnabled) {
  38. //make sure each setting is set
  39. result= this.Settings.IsEnabled;
  40. //TODO: Figure out a check for required values
  41. }
  42. return result;
  43. }
  44. }
  45. public string PluginName {
  46. get {
  47. return this.GetType().Name;
  48. }
  49. }
  50. public string Code {
  51. get {
  52. return FilePath.GetFileText();
  53. }
  54. }
  55. string _filePath = "";
  56. public string FilePath {
  57. get {
  58. if (string.IsNullOrEmpty(_filePath)) {
  59. string directoryRoot = HttpContext.Current.Server.MapPath("~/App_Code");
  60. _filePath=this.GetType().Name.LocateFilePath(directoryRoot);
  61. }
  62. return _filePath;
  63. }
  64. set {
  65. _filePath = value;
  66. }
  67. }
  68. public static void LoadSettings() {
  69. ObjectStore store = new ObjectStore();
  70. _settings=store.GetList<PluginSetting>("PluginSetting");
  71. }
  72. public static void ValidateSetting(string pluginName, string settingName, object value) {
  73. var plugin = Plugins.Where(x => x.GetType().Name.Equals(pluginName, StringComparison.InvariantCultureIgnoreCase)).SingleOrDefault();
  74. if (plugin != null) {
  75. PropertyInfo prop = plugin.GetType().GetProperty(settingName);
  76. //see if it can be assigned this value
  77. if (prop != null) {
  78. //set the value to see if it can be coerced
  79. try {
  80. value.ChangeType(prop.PropertyType);
  81. } catch {
  82. throw new InvalidOperationException("Can't assign " + value.ToString() + " to " + prop.PropertyType.Name);
  83. }
  84. } else {
  85. throw new InvalidOperationException("No setting found to validate with that name");
  86. }
  87. } else {
  88. throw new InvalidOperationException("No plugin found to validate with that name");
  89. }
  90. }
  91. /// <summary>
  92. /// Optionally override this in a plug-in to add an initialization step.
  93. /// </summary>
  94. public virtual void Initialize() { }
  95. //initializes based on App_Code
  96. public static IList<Plugin> InitializePlugins() {
  97. var codeAssemblies = System.Web.Compilation.BuildManager.CodeAssemblies;
  98. return InitializePlugins(codeAssemblies);
  99. }
  100. public object GetSetting(string name) {
  101. //see if this setting is presetn
  102. object result = null;
  103. if (this.Settings.Settings.ContainsKey(name))
  104. result = this.Settings.Settings[name];
  105. return result;
  106. }
  107. //TODO: Fix this to be less arrow-y
  108. /// <summary>
  109. /// Discovers and initializes the plug-ins.
  110. /// </summary>
  111. public static IList<Plugin> InitializePlugins(IList codeAssemblies) {
  112. if (s_inited)
  113. return null;
  114. LoadSettings();
  115. lock (s_initLock) {
  116. if (!s_inited) {
  117. if (codeAssemblies != null) {
  118. foreach (Assembly assembly in codeAssemblies) {
  119. if (!assembly.FullName.StartsWith("System") & !assembly.FullName.StartsWith("Microsoft.VisualStudio")) {
  120. try {
  121. foreach (var type in assembly.GetTypes()) {
  122. if (typeof(Plugin).IsAssignableFrom(type) && type != typeof(Plugin)) {
  123. var plugin = (Plugin)Activator.CreateInstance(type);
  124. plugin.Initialize();
  125. _plugins.Add(plugin);
  126. }
  127. }
  128. }
  129. catch (Exception x) {
  130. if (x is System.Reflection.ReflectionTypeLoadException) {
  131. var newX = x as System.Reflection.ReflectionTypeLoadException;
  132. throw new InvalidOperationException(newX.LoaderExceptions[0].Message);
  133. }
  134. else {
  135. throw;
  136. }
  137. }
  138. }
  139. }
  140. }
  141. s_inited = true;
  142. }
  143. }
  144. return _plugins;
  145. }
  146. /// <summary>
  147. /// The list of installed plug-ins.
  148. /// </summary>
  149. public static Plugin[] Plugins {
  150. get {
  151. return _plugins.ToArray();
  152. }
  153. }
  154. public static TResult Execute<TResult>(string pluginName, string operation, params object[] input) {
  155. MethodInfo method = null;
  156. TResult result = default(TResult);
  157. //find the plugin
  158. var plugin = _plugins.Where(x => x.GetType().Name.Equals(pluginName, StringComparison.InvariantCultureIgnoreCase)).SingleOrDefault();
  159. if (plugin != null) {
  160. method = plugin.GetType().GetMethods().FirstOrDefault(x => x.Name == operation);
  161. if (method != null) {
  162. result = (TResult)method.Invoke(plugin, input);
  163. }
  164. //load the first
  165. }
  166. return result;
  167. }
  168. public static TResult ExecuteFirst<TResult>(string operation, params object[] input) {
  169. MethodInfo method = null;
  170. Plugin foundPlugin=null;
  171. TResult result = default(TResult);
  172. foreach (var plugin in _plugins) {
  173. method = plugin.GetType().GetMethods().FirstOrDefault(x => x.Name == operation);
  174. if (method != null){
  175. foundPlugin=plugin;
  176. break;
  177. }
  178. }
  179. if (method != null) {
  180. result = (TResult)method.Invoke(foundPlugin, input);
  181. }
  182. //load the first
  183. return result;
  184. }
  185. /// <summary>
  186. /// Gets a method that calls all the plug-ins of the provided signature.
  187. /// </summary>
  188. /// <typeparam name="T">A delegate type describing the expected signature.</typeparam>
  189. /// <returns>A method that calls all the plug-ins with the signature of T.</returns>
  190. public static T GetMethod<T>(string operation) where T : class {
  191. var operationMethods = _methods[operation] as Hashtable;
  192. var operationCached = _cached[operation] as Hashtable;
  193. if (operationMethods == null) {
  194. operationMethods = Hashtable.Synchronized(new Hashtable());
  195. _methods.Add(operation, operationMethods);
  196. operationCached = Hashtable.Synchronized(new Hashtable());
  197. _cached.Add(operation, operationCached);
  198. }
  199. var processDelegate = (Delegate)operationMethods[typeof(T)];
  200. if (processDelegate == null) {
  201. if (operationCached[typeof(T)] == null) {
  202. foreach (var plugin in _plugins) {
  203. var pluginType = plugin.GetType();
  204. var processMethod = pluginType.GetMethod(operation,
  205. BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
  206. if (processMethod == null) continue;
  207. var newDelegate = Delegate.CreateDelegate(typeof(T), plugin, processMethod, false);
  208. if (newDelegate != null) {
  209. if (processDelegate == null) {
  210. processDelegate = newDelegate;
  211. }
  212. else {
  213. processDelegate = Delegate.Combine(processDelegate, newDelegate);
  214. }
  215. }
  216. }
  217. operationMethods[typeof(T)] = processDelegate;
  218. operationCached[typeof(T)] = true;
  219. }
  220. }
  221. return (T)(object)processDelegate;
  222. }
  223. /// <summary>
  224. /// Feeds the provided input through all plug-ins that have T [operation](string, T)
  225. /// as their signature, where [operation] is the contents of the operation parameter.
  226. /// </summary>
  227. /// <typeparam name="T">The type of the input to process.</typeparam>
  228. /// <param name="operation">The key of the operation to perform.</param>
  229. /// <param name="input">The object to process.</param>
  230. /// <returns>The processed object.</returns>
  231. public static T Process<T>(string operation, T input) where T : class {
  232. var methods = GetMethod<Func<T, T>>(operation);
  233. if (methods != null) {
  234. foreach (Func<T, T> pluginMethod in methods.GetInvocationList()) {
  235. if (input == null) return null;
  236. input = pluginMethod(input);
  237. }
  238. }
  239. return input;
  240. }
  241. /// <summary>
  242. /// Puts the provided object through all plug-ins with signature
  243. /// bool Process(string, T). The return value is false if any of the
  244. /// plug-ins returned false, and true otherwise. This is the pattern
  245. /// to use for pluggable validation logic for example.
  246. /// </summary>
  247. /// <typeparam name="T">The type of the input to process.</typeparam>
  248. /// <param name="operation">The key of the operation to perform.</param>
  249. /// <param name="input">The object to process.</param>
  250. /// <returns>False if any of the plug-ins returned false.</returns>
  251. public static bool IsTrue<T>(string operation, T input) {
  252. Delegate methods = GetMethod<Func<T, bool>>(operation);
  253. if (methods == null) return true;
  254. foreach (var pluginMethod in methods.GetInvocationList()) {
  255. if (!(bool)pluginMethod.DynamicInvoke(input)) {
  256. return false;
  257. }
  258. }
  259. return true;
  260. }
  261. }
  262. }