PageRenderTime 25ms CodeModel.GetById 14ms app.highlight 8ms RepoModel.GetById 1ms 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
  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}