- Fixed MR-84
[castle.git] / MonoRail / Castle.MonoRail.Framework / Services / DefaultControllerDescriptorProvider.cs
blob4bce12f7dba3a6b5a0ba27321f11e263015bbbc9
1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework.Services
17 using System;
18 using System.Collections;
19 using System.Reflection;
20 using System.Threading;
22 using Castle.Core.Logging;
23 using Castle.MonoRail.Framework.Internal;
25 /// <summary>
26 /// Constructs and caches all collected information
27 /// about a <see cref="Controller"/> and its actions.
28 /// <seealso cref="ControllerMetaDescriptor"/>
29 /// </summary>
30 public class DefaultControllerDescriptorProvider : IControllerDescriptorProvider
32 /// <summary>
33 /// The logger instance
34 /// </summary>
35 private ILogger logger = NullLogger.Instance;
37 /// <summary>
38 /// Used to lock the cache
39 /// </summary>
40 private ReaderWriterLock locker = new ReaderWriterLock();
41 private Hashtable descriptorRepository = new Hashtable();
42 private IHelperDescriptorProvider helperDescriptorProvider;
43 private IFilterDescriptorProvider filterDescriptorProvider;
44 private ILayoutDescriptorProvider layoutDescriptorProvider;
45 private IRescueDescriptorProvider rescueDescriptorProvider;
46 private IResourceDescriptorProvider resourceDescriptorProvider;
47 private ITransformFilterDescriptorProvider transformFilterDescriptorProvider;
49 #region IServiceEnabledComponent implementation
51 /// <summary>
52 /// Services the specified service provider.
53 /// </summary>
54 /// <param name="serviceProvider">The service provider.</param>
55 public void Service(IServiceProvider serviceProvider)
57 ILoggerFactory loggerFactory = (ILoggerFactory) serviceProvider.GetService(typeof(ILoggerFactory));
59 if (loggerFactory != null)
61 logger = loggerFactory.Create(typeof(DefaultControllerDescriptorProvider));
64 helperDescriptorProvider = (IHelperDescriptorProvider)
65 serviceProvider.GetService(typeof(IHelperDescriptorProvider));
67 filterDescriptorProvider = (IFilterDescriptorProvider)
68 serviceProvider.GetService(typeof(IFilterDescriptorProvider));
70 layoutDescriptorProvider = (ILayoutDescriptorProvider)
71 serviceProvider.GetService(typeof(ILayoutDescriptorProvider));
73 rescueDescriptorProvider = (IRescueDescriptorProvider)
74 serviceProvider.GetService(typeof(IRescueDescriptorProvider));
76 resourceDescriptorProvider = (IResourceDescriptorProvider)
77 serviceProvider.GetService(typeof(IResourceDescriptorProvider));
79 transformFilterDescriptorProvider = (ITransformFilterDescriptorProvider)
80 serviceProvider.GetService(typeof(ITransformFilterDescriptorProvider));
83 #endregion
85 /// <summary>
86 /// Constructs and populates a <see cref="ControllerMetaDescriptor"/>.
87 /// </summary>
88 /// <remarks>
89 /// This implementation is also responsible for caching
90 /// constructed meta descriptors.
91 /// </remarks>
92 public ControllerMetaDescriptor BuildDescriptor(Controller controller)
94 Type controllerType = controller.GetType();
96 return BuildDescriptor(controllerType);
99 /// <summary>
100 /// Constructs and populates a <see cref="ControllerMetaDescriptor"/>.
101 /// </summary>
102 /// <remarks>
103 /// This implementation is also responsible for caching
104 /// constructed meta descriptors.
105 /// </remarks>
106 public ControllerMetaDescriptor BuildDescriptor(Type controllerType)
108 ControllerMetaDescriptor desc;
110 locker.AcquireReaderLock(-1);
112 desc = (ControllerMetaDescriptor) descriptorRepository[controllerType];
114 if (desc != null)
116 locker.ReleaseReaderLock();
117 return desc;
122 locker.UpgradeToWriterLock(-1);
124 // We need to recheck after getting the writer lock
125 desc = (ControllerMetaDescriptor) descriptorRepository[controllerType];
127 if (desc != null)
129 return desc;
132 desc = InternalBuildDescriptor(controllerType);
134 descriptorRepository[controllerType] = desc;
136 finally
138 locker.ReleaseWriterLock();
141 return desc;
144 /// <summary>
145 /// Builds the <see cref="ControllerMetaDescriptor"/> for the specified controller type
146 /// </summary>
147 /// <param name="controllerType">Type of the controller.</param>
148 /// <returns></returns>
149 private ControllerMetaDescriptor InternalBuildDescriptor(Type controllerType)
151 if (logger.IsDebugEnabled)
153 logger.DebugFormat("Building controller descriptor for {0}", controllerType);
156 ControllerMetaDescriptor descriptor = new ControllerMetaDescriptor();
158 CollectClassLevelAttributes(controllerType, descriptor);
160 CollectActions(controllerType, descriptor);
162 CollectActionLevelAttributes(descriptor);
164 return descriptor;
167 #region Action data
169 /// <summary>
170 /// Collects the actions.
171 /// </summary>
172 /// <param name="controllerType">Type of the controller.</param>
173 /// <param name="desc">The desc.</param>
174 private void CollectActions(Type controllerType, ControllerMetaDescriptor desc)
176 // HACK: GetRealControllerType is a workaround for DYNPROXY-14 bug
177 // see: http://support.castleproject.org/browse/DYNPROXY-14
178 controllerType = GetRealControllerType(controllerType);
180 MethodInfo[] methods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
182 foreach(MethodInfo method in methods)
184 Type declaringType = method.DeclaringType;
186 if (declaringType == typeof(Object) ||
187 declaringType == typeof(Controller) ||
188 declaringType == typeof(SmartDispatcherController))
190 continue;
193 if (desc.Actions.Contains(method.Name))
195 ArrayList list = desc.Actions[method.Name] as ArrayList;
197 if (list == null)
199 list = new ArrayList();
200 list.Add(desc.Actions[method.Name]);
202 desc.Actions[method.Name] = list;
205 list.Add(method);
207 else
209 desc.Actions[method.Name] = method;
214 /// <summary>
215 /// Collects the action level attributes.
216 /// </summary>
217 /// <param name="descriptor">The descriptor.</param>
218 private void CollectActionLevelAttributes(ControllerMetaDescriptor descriptor)
220 foreach(object action in descriptor.Actions.Values)
222 if (action is IList)
224 foreach(MethodInfo overloadedAction in (action as IList))
226 CollectActionAttributes(overloadedAction, descriptor);
229 continue;
232 CollectActionAttributes(action as MethodInfo, descriptor);
236 /// <summary>
237 /// Collects the action attributes.
238 /// </summary>
239 /// <param name="method">The method.</param>
240 /// <param name="descriptor">The descriptor.</param>
241 private void CollectActionAttributes(MethodInfo method, ControllerMetaDescriptor descriptor)
243 if (logger.IsDebugEnabled)
245 logger.DebugFormat("Collection attributes for action {0}", method.Name);
248 ActionMetaDescriptor actionDescriptor = descriptor.GetAction(method);
250 CollectResources(actionDescriptor, method);
251 CollectSkipFilter(actionDescriptor, method);
252 CollectRescues(actionDescriptor, method);
253 CollectAccessibleThrough(actionDescriptor, method);
254 CollectSkipRescue(actionDescriptor, method);
255 CollectLayout(actionDescriptor, method);
256 CollectCacheConfigures(actionDescriptor, method);
257 CollectTransformFilter(actionDescriptor, method);
259 if (method.IsDefined(typeof(AjaxActionAttribute), true))
261 descriptor.AjaxActions.Add(method);
264 if (method.IsDefined(typeof(DefaultActionAttribute), true))
266 if (descriptor.DefaultAction != null)
268 throw new MonoRailException("Cannot resolve a default action for {0}, DefaultActionAttribute was declared more than once.", method.DeclaringType.FullName);
270 descriptor.DefaultAction = new DefaultActionAttribute(method.Name);
274 /// <summary>
275 /// Collects the skip rescue.
276 /// </summary>
277 /// <param name="actionDescriptor">The action descriptor.</param>
278 /// <param name="method">The method.</param>
279 private void CollectSkipRescue(ActionMetaDescriptor actionDescriptor, MethodInfo method)
281 object[] attributes = method.GetCustomAttributes(typeof(SkipRescueAttribute), true);
283 if (attributes.Length != 0)
285 actionDescriptor.SkipRescue = (SkipRescueAttribute) attributes[0];
289 /// <summary>
290 /// Collects the accessible through.
291 /// </summary>
292 /// <param name="actionDescriptor">The action descriptor.</param>
293 /// <param name="method">The method.</param>
294 private void CollectAccessibleThrough(ActionMetaDescriptor actionDescriptor, MethodInfo method)
296 object[] attributes = method.GetCustomAttributes(typeof(AccessibleThroughAttribute), true);
298 if (attributes.Length != 0)
300 actionDescriptor.AccessibleThrough = (AccessibleThroughAttribute) attributes[0];
304 /// <summary>
305 /// Collects the skip filter.
306 /// </summary>
307 /// <param name="actionDescriptor">The action descriptor.</param>
308 /// <param name="method">The method.</param>
309 private void CollectSkipFilter(ActionMetaDescriptor actionDescriptor, MethodInfo method)
311 object[] attributes = method.GetCustomAttributes(typeof(SkipFilterAttribute), true);
313 foreach(SkipFilterAttribute attr in attributes)
315 actionDescriptor.SkipFilters.Add(attr);
319 /// <summary>
320 /// Collects the resources.
321 /// </summary>
322 /// <param name="desc">The desc.</param>
323 /// <param name="memberInfo">The member info.</param>
324 private void CollectResources(BaseMetaDescriptor desc, MemberInfo memberInfo)
326 desc.Resources = resourceDescriptorProvider.CollectResources(memberInfo);
329 /// <summary>
330 /// Collects the transform filter.
331 /// </summary>
332 /// <param name="actionDescriptor">The action descriptor.</param>
333 /// <param name="method">The method.</param>
334 private void CollectTransformFilter(ActionMetaDescriptor actionDescriptor, MethodInfo method)
336 actionDescriptor.TransformFilters = transformFilterDescriptorProvider.CollectFilters((method));
337 Array.Sort(actionDescriptor.TransformFilters, TransformFilterDescriptorComparer.Instance);
340 /// <summary>
341 /// Gets the real controller type, instead of the proxy type.
342 /// </summary>
343 /// <remarks>
344 /// Workaround for DYNPROXY-14 bug. See: http://support.castleproject.org/browse/DYNPROXY-14
345 /// </remarks>
346 private Type GetRealControllerType(Type controllerType)
348 Type prev = controllerType;
350 // try to get the first non-proxy type
351 while(controllerType.Assembly.FullName.StartsWith("DynamicProxyGenAssembly2") ||
352 controllerType.Assembly.FullName.StartsWith("DynamicAssemblyProxyGen"))
354 controllerType = controllerType.BaseType;
356 if (controllerType == typeof(SmartDispatcherController) ||
357 controllerType == typeof(Controller))
359 // oops, it's a pure-proxy controller. just let it go.
360 controllerType = prev;
361 break;
365 return controllerType;
368 #endregion
370 #region Controller data
372 /// <summary>
373 /// Collects the class level attributes.
374 /// </summary>
375 /// <param name="controllerType">Type of the controller.</param>
376 /// <param name="descriptor">The descriptor.</param>
377 private void CollectClassLevelAttributes(Type controllerType, ControllerMetaDescriptor descriptor)
379 CollectHelpers(descriptor, controllerType);
380 CollectResources(descriptor, controllerType);
381 CollectFilters(descriptor, controllerType);
382 CollectLayout(descriptor, controllerType);
383 CollectRescues(descriptor, controllerType);
384 CollectDefaultAction(descriptor, controllerType);
385 CollectScaffolding(descriptor, controllerType);
386 CollectDynamicAction(descriptor, controllerType);
389 /// <summary>
390 /// Collects the default action.
391 /// </summary>
392 /// <param name="descriptor">The descriptor.</param>
393 /// <param name="controllerType">Type of the controller.</param>
394 private void CollectDefaultAction(ControllerMetaDescriptor descriptor, Type controllerType)
396 object[] attributes = controllerType.GetCustomAttributes(typeof(DefaultActionAttribute), true);
398 if (attributes.Length != 0)
400 descriptor.DefaultAction = (DefaultActionAttribute) attributes[0];
404 /// <summary>
405 /// Collects the scaffolding.
406 /// </summary>
407 /// <param name="descriptor">The descriptor.</param>
408 /// <param name="controllerType">Type of the controller.</param>
409 private void CollectScaffolding(ControllerMetaDescriptor descriptor, Type controllerType)
411 object[] attributes = controllerType.GetCustomAttributes(typeof(ScaffoldingAttribute), false);
413 if (attributes.Length != 0)
415 foreach(ScaffoldingAttribute scaffolding in attributes)
417 descriptor.Scaffoldings.Add(scaffolding);
422 /// <summary>
423 /// Collects the dynamic action.
424 /// </summary>
425 /// <param name="descriptor">The descriptor.</param>
426 /// <param name="controllerType">Type of the controller.</param>
427 private void CollectDynamicAction(ControllerMetaDescriptor descriptor, Type controllerType)
429 object[] attributes = controllerType.GetCustomAttributes(typeof(DynamicActionProviderAttribute), true);
431 if (attributes.Length != 0)
433 foreach(DynamicActionProviderAttribute attr in attributes)
435 descriptor.ActionProviders.Add(attr.ProviderType);
440 /// <summary>
441 /// Collects the helpers.
442 /// </summary>
443 /// <param name="descriptor">The descriptor.</param>
444 /// <param name="controllerType">Type of the controller.</param>
445 private void CollectHelpers(ControllerMetaDescriptor descriptor, Type controllerType)
447 descriptor.Helpers = helperDescriptorProvider.CollectHelpers(controllerType);
450 /// <summary>
451 /// Collects the filters.
452 /// </summary>
453 /// <param name="descriptor">The descriptor.</param>
454 /// <param name="controllerType">Type of the controller.</param>
455 private void CollectFilters(ControllerMetaDescriptor descriptor, Type controllerType)
457 descriptor.Filters = filterDescriptorProvider.CollectFilters(controllerType);
459 Array.Sort(descriptor.Filters, FilterDescriptorComparer.Instance);
462 /// <summary>
463 /// Collects the layout.
464 /// </summary>
465 /// <param name="descriptor">The descriptor.</param>
466 /// <param name="memberInfo">The member info.</param>
467 private void CollectLayout(BaseMetaDescriptor descriptor, MemberInfo memberInfo)
469 descriptor.Layout = layoutDescriptorProvider.CollectLayout(memberInfo);
472 /// <summary>
473 /// Collects the rescues.
474 /// </summary>
475 /// <param name="descriptor">The descriptor.</param>
476 /// <param name="memberInfo">The member info.</param>
477 private void CollectRescues(BaseMetaDescriptor descriptor, MethodInfo memberInfo)
479 descriptor.Rescues = rescueDescriptorProvider.CollectRescues(memberInfo);
482 /// <summary>
483 /// Collects the rescues.
484 /// </summary>
485 /// <param name="descriptor">The descriptor.</param>
486 /// <param name="type">The type.</param>
487 private void CollectRescues(BaseMetaDescriptor descriptor, Type type)
489 descriptor.Rescues = rescueDescriptorProvider.CollectRescues(type);
492 /// <summary>
493 /// Collects the cache configures.
494 /// </summary>
495 /// <param name="descriptor">The descriptor.</param>
496 /// <param name="memberInfo">The member info.</param>
497 private void CollectCacheConfigures(ActionMetaDescriptor descriptor, MemberInfo memberInfo)
499 object[] configurers = memberInfo.GetCustomAttributes(typeof(ICachePolicyConfigurer), true);
501 if (configurers.Length != 0)
503 foreach(ICachePolicyConfigurer cacheConfigurer in configurers)
505 descriptor.CacheConfigurers.Add(cacheConfigurer);
510 #endregion
512 /// <summary>
513 /// This <see cref="IComparer"/> implementation
514 /// is used to sort the filters based on their Execution Order.
515 /// </summary>
516 class FilterDescriptorComparer : IComparer
518 private static readonly FilterDescriptorComparer instance = new FilterDescriptorComparer();
520 /// <summary>
521 /// Initializes a new instance of the <see cref="FilterDescriptorComparer"/> class.
522 /// </summary>
523 private FilterDescriptorComparer()
527 /// <summary>
528 /// Gets the instance.
529 /// </summary>
530 /// <value>The instance.</value>
531 public static FilterDescriptorComparer Instance
533 get { return instance; }
536 /// <summary>
537 /// Compares the specified left.
538 /// </summary>
539 /// <param name="left">The left.</param>
540 /// <param name="right">The right.</param>
541 /// <returns></returns>
542 public int Compare(object left, object right)
544 return ((FilterDescriptor) left).ExecutionOrder - ((FilterDescriptor) right).ExecutionOrder;
548 /// <summary>
549 /// This <see cref="IComparer"/> implementation
550 /// is used to sort the transformfilters based on their Execution Order.
551 /// </summary>
552 class TransformFilterDescriptorComparer : IComparer
554 private static readonly TransformFilterDescriptorComparer instance = new TransformFilterDescriptorComparer();
556 /// <summary>
557 /// Initializes a new instance of the <see cref="TransformFilterDescriptorComparer"/> class.
558 /// </summary>
559 private TransformFilterDescriptorComparer()
563 /// <summary>
564 /// Gets the instance.
565 /// </summary>
566 /// <value>The instance.</value>
567 public static TransformFilterDescriptorComparer Instance
569 get { return instance; }
572 /// <summary>
573 /// Compares the specified left.
574 /// </summary>
575 /// <param name="left">The left.</param>
576 /// <param name="right">The right.</param>
577 /// <returns></returns>
578 public int Compare(object left, object right)
580 return ((TransformFilterDescriptor)right).ExecutionOrder - ((TransformFilterDescriptor)left).ExecutionOrder;