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