More working tests.
[castle.git] / MonoRail / Castle.MonoRail.Framework / Providers / DefaultControllerDescriptorProvider.cs
blobe72b15a37c5ed0b86358d1e420cd1d2ada5ad7e7
1 // Copyright 2004-2008 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.Providers
17 using System;
18 using System.Collections;
19 using System.Reflection;
20 using System.Threading;
21 using Castle.Core.Logging;
22 using Castle.MonoRail.Framework.Services.Utils;
23 using Descriptors;
24 using Providers;
26 /// <summary>
27 /// Constructs and caches all collected information
28 /// about a <see cref="IController"/> and its actions.
29 /// <seealso cref="ControllerMetaDescriptor"/>
30 /// </summary>
31 public class DefaultControllerDescriptorProvider : IControllerDescriptorProvider
33 /// <summary>
34 /// The logger instance
35 /// </summary>
36 private ILogger logger = NullLogger.Instance;
38 /// <summary>
39 /// Used to lock the cache
40 /// </summary>
41 private ReaderWriterLock locker = new ReaderWriterLock();
43 private Hashtable descriptorRepository = new Hashtable();
44 private IHelperDescriptorProvider helperDescriptorProvider;
45 private IFilterDescriptorProvider filterDescriptorProvider;
46 private ILayoutDescriptorProvider layoutDescriptorProvider;
47 private IRescueDescriptorProvider rescueDescriptorProvider;
48 private IResourceDescriptorProvider resourceDescriptorProvider;
49 private ITransformFilterDescriptorProvider transformFilterDescriptorProvider;
51 /// <summary>
52 /// Initializes a new instance of the <see cref="DefaultControllerDescriptorProvider"/> class.
53 /// </summary>
54 public DefaultControllerDescriptorProvider()
58 /// <summary>
59 /// Initializes a new instance of the <see cref="DefaultControllerDescriptorProvider"/> class.
60 /// </summary>
61 /// <param name="helperDescriptorProvider">The helper descriptor provider.</param>
62 /// <param name="filterDescriptorProvider">The filter descriptor provider.</param>
63 /// <param name="layoutDescriptorProvider">The layout descriptor provider.</param>
64 /// <param name="rescueDescriptorProvider">The rescue descriptor provider.</param>
65 /// <param name="resourceDescriptorProvider">The resource descriptor provider.</param>
66 /// <param name="transformFilterDescriptorProvider">The transform filter descriptor provider.</param>
67 public DefaultControllerDescriptorProvider(IHelperDescriptorProvider helperDescriptorProvider,
68 IFilterDescriptorProvider filterDescriptorProvider,
69 ILayoutDescriptorProvider layoutDescriptorProvider,
70 IRescueDescriptorProvider rescueDescriptorProvider,
71 IResourceDescriptorProvider resourceDescriptorProvider,
72 ITransformFilterDescriptorProvider transformFilterDescriptorProvider)
74 this.helperDescriptorProvider = helperDescriptorProvider;
75 this.filterDescriptorProvider = filterDescriptorProvider;
76 this.layoutDescriptorProvider = layoutDescriptorProvider;
77 this.rescueDescriptorProvider = rescueDescriptorProvider;
78 this.resourceDescriptorProvider = resourceDescriptorProvider;
79 this.transformFilterDescriptorProvider = transformFilterDescriptorProvider;
82 #region IMRServiceEnabled implementation
84 /// <summary>
85 /// Services the specified service provider.
86 /// </summary>
87 /// <param name="serviceProvider">The service provider.</param>
88 public void Service(IMonoRailServices serviceProvider)
90 ILoggerFactory loggerFactory = (ILoggerFactory) serviceProvider.GetService(typeof(ILoggerFactory));
92 if (loggerFactory != null)
94 logger = loggerFactory.Create(typeof(DefaultControllerDescriptorProvider));
97 helperDescriptorProvider = serviceProvider.GetService<IHelperDescriptorProvider>();
98 filterDescriptorProvider = serviceProvider.GetService<IFilterDescriptorProvider>();
99 layoutDescriptorProvider = serviceProvider.GetService<ILayoutDescriptorProvider>();
100 rescueDescriptorProvider = serviceProvider.GetService<IRescueDescriptorProvider>();
101 resourceDescriptorProvider = serviceProvider.GetService<IResourceDescriptorProvider>();
102 transformFilterDescriptorProvider = serviceProvider.GetService<ITransformFilterDescriptorProvider>();
105 #endregion
107 /// <summary>
108 /// Occurs when the providers needs to create a <see cref="ControllerMetaDescriptor"/>.
109 /// </summary>
110 public event MetaCreatorHandler Create;
112 /// <summary>
113 /// Occurs when the meta descriptor is about to the returned to the caller.
114 /// </summary>
115 public event ControllerMetaDescriptorHandler AfterProcess;
117 /// <summary>
118 /// Occurs when the providers needs to create a <see cref="ActionMetaDescriptor"/>.
119 /// </summary>
120 public event ActionMetaCreatorHandler ActionCreate;
122 /// <summary>
123 /// Occurs when the meta descriptor is about to be included on the <see cref="ControllerMetaDescriptor"/>.
124 /// </summary>
125 public event ActionMetaDescriptorHandler AfterActionProcess;
127 /// <summary>
128 /// Constructs and populates a <see cref="ControllerMetaDescriptor"/>.
129 /// </summary>
130 /// <remarks>
131 /// This implementation is also responsible for caching
132 /// constructed meta descriptors.
133 /// </remarks>
134 public ControllerMetaDescriptor BuildDescriptor(IController controller)
136 Type controllerType = controller.GetType();
138 return BuildDescriptor(controllerType);
141 /// <summary>
142 /// Constructs and populates a <see cref="ControllerMetaDescriptor"/>.
143 /// </summary>
144 /// <remarks>
145 /// This implementation is also responsible for caching
146 /// constructed meta descriptors.
147 /// </remarks>
148 public ControllerMetaDescriptor BuildDescriptor(Type controllerType)
150 ControllerMetaDescriptor desc;
152 locker.AcquireReaderLock(-1);
154 desc = (ControllerMetaDescriptor) descriptorRepository[controllerType];
156 if (desc != null)
158 locker.ReleaseReaderLock();
159 return desc;
164 locker.UpgradeToWriterLock(-1);
166 // We need to recheck after getting the writer lock
167 desc = (ControllerMetaDescriptor) descriptorRepository[controllerType];
169 if (desc != null)
171 return desc;
174 desc = InternalBuildDescriptor(controllerType);
176 descriptorRepository[controllerType] = desc;
178 finally
180 locker.ReleaseWriterLock();
183 return desc;
186 /// <summary>
187 /// Builds the <see cref="ControllerMetaDescriptor"/> for the specified controller type
188 /// </summary>
189 /// <param name="controllerType">Type of the controller.</param>
190 /// <returns></returns>
191 private ControllerMetaDescriptor InternalBuildDescriptor(Type controllerType)
193 if (logger.IsDebugEnabled)
195 logger.DebugFormat("Building controller descriptor for {0}", controllerType);
198 ControllerMetaDescriptor descriptor = CreateMetaDescriptor();
200 descriptor.ControllerDescriptor = ControllerInspectionUtil.Inspect(controllerType);
202 CollectClassLevelAttributes(controllerType, descriptor);
204 CollectActions(controllerType, descriptor);
206 CollectActionLevelAttributes(descriptor);
208 if (AfterProcess != null)
210 AfterProcess(descriptor);
213 return descriptor;
216 #region Action data
218 /// <summary>
219 /// Collects the actions.
220 /// </summary>
221 /// <param name="controllerType">Type of the controller.</param>
222 /// <param name="desc">The desc.</param>
223 private void CollectActions(Type controllerType, ControllerMetaDescriptor desc)
225 // HACK: GetRealControllerType is a workaround for DYNPROXY-14 bug
226 // see: http://support.castleproject.org/browse/DYNPROXY-14
227 controllerType = GetRealControllerType(controllerType);
229 MethodInfo[] methods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
231 foreach(MethodInfo method in methods)
233 Type declaringType = method.DeclaringType;
235 if (method.IsSpecialName) continue;
237 if (declaringType == typeof(Object) ||
238 declaringType == typeof(IController) || declaringType == typeof(Controller))
239 // || declaringType == typeof(SmartDispatcherController))
241 continue;
244 if (desc.Actions.Contains(method.Name))
246 ArrayList list = desc.Actions[method.Name] as ArrayList;
248 if (list == null)
250 list = new ArrayList();
251 list.Add(desc.Actions[method.Name]);
253 desc.Actions[method.Name] = list;
256 list.Add(method);
258 else
260 desc.Actions[method.Name] = method;
265 /// <summary>
266 /// Collects the action level attributes.
267 /// </summary>
268 /// <param name="descriptor">The descriptor.</param>
269 private void CollectActionLevelAttributes(ControllerMetaDescriptor descriptor)
271 foreach(object action in descriptor.Actions.Values)
273 if (action is IList)
275 foreach(MethodInfo overloadedAction in (action as IList))
277 CollectActionAttributes(overloadedAction, descriptor);
280 continue;
283 CollectActionAttributes(action as MethodInfo, descriptor);
287 /// <summary>
288 /// Collects the action attributes.
289 /// </summary>
290 /// <param name="method">The method.</param>
291 /// <param name="descriptor">The descriptor.</param>
292 private void CollectActionAttributes(MethodInfo method, ControllerMetaDescriptor descriptor)
294 if (logger.IsDebugEnabled)
296 logger.DebugFormat("Collection attributes for action {0}", method.Name);
299 ActionMetaDescriptor actionDescriptor = descriptor.GetAction(method);
301 if (actionDescriptor == null)
303 actionDescriptor = CreateActionDescriptor();
305 descriptor.AddAction(method, actionDescriptor);
308 CollectResources(actionDescriptor, method);
309 CollectSkipFilter(actionDescriptor, method);
310 CollectRescues(actionDescriptor, method);
311 CollectAccessibleThrough(actionDescriptor, method);
312 CollectSkipRescue(actionDescriptor, method);
313 CollectLayout(actionDescriptor, method);
314 CollectCacheConfigure(actionDescriptor, method);
315 CollectTransformFilter(actionDescriptor, method);
317 if (method.IsDefined(typeof(AjaxActionAttribute), true))
319 descriptor.AjaxActions.Add(method);
322 if (method.IsDefined(typeof(DefaultActionAttribute), true))
324 if (descriptor.DefaultAction != null)
326 throw new MonoRailException(
327 "Cannot resolve a default action for {0}, DefaultActionAttribute was declared more than once.",
328 method.DeclaringType.FullName);
330 descriptor.DefaultAction = new DefaultActionAttribute(method.Name);
333 if (AfterActionProcess != null)
335 AfterActionProcess(actionDescriptor);
339 /// <summary>
340 /// Collects the skip rescue.
341 /// </summary>
342 /// <param name="actionDescriptor">The action descriptor.</param>
343 /// <param name="method">The method.</param>
344 private void CollectSkipRescue(ActionMetaDescriptor actionDescriptor, MethodInfo method)
346 object[] attributes = method.GetCustomAttributes(typeof(SkipRescueAttribute), true);
348 if (attributes.Length != 0)
350 actionDescriptor.SkipRescue = (SkipRescueAttribute) attributes[0];
354 /// <summary>
355 /// Collects the accessible through.
356 /// </summary>
357 /// <param name="actionDescriptor">The action descriptor.</param>
358 /// <param name="method">The method.</param>
359 private void CollectAccessibleThrough(ActionMetaDescriptor actionDescriptor, MethodInfo method)
361 object[] attributes = method.GetCustomAttributes(typeof(AccessibleThroughAttribute), true);
363 if (attributes.Length != 0)
365 actionDescriptor.AccessibleThrough = (AccessibleThroughAttribute) attributes[0];
369 /// <summary>
370 /// Collects the skip filter.
371 /// </summary>
372 /// <param name="actionDescriptor">The action descriptor.</param>
373 /// <param name="method">The method.</param>
374 private void CollectSkipFilter(ActionMetaDescriptor actionDescriptor, MethodInfo method)
376 object[] attributes = method.GetCustomAttributes(typeof(SkipFilterAttribute), true);
378 foreach(SkipFilterAttribute attr in attributes)
380 actionDescriptor.SkipFilters.Add(attr);
384 /// <summary>
385 /// Collects the resources.
386 /// </summary>
387 /// <param name="desc">The desc.</param>
388 /// <param name="memberInfo">The member info.</param>
389 private void CollectResources(BaseMetaDescriptor desc, MemberInfo memberInfo)
391 desc.Resources = resourceDescriptorProvider.CollectResources(memberInfo);
394 /// <summary>
395 /// Collects the transform filter.
396 /// </summary>
397 /// <param name="actionDescriptor">The action descriptor.</param>
398 /// <param name="method">The method.</param>
399 private void CollectTransformFilter(ActionMetaDescriptor actionDescriptor, MethodInfo method)
401 actionDescriptor.TransformFilters = transformFilterDescriptorProvider.CollectFilters((method));
402 Array.Sort(actionDescriptor.TransformFilters, TransformFilterDescriptorComparer.Instance);
405 /// <summary>
406 /// Gets the real controller type, instead of the proxy type.
407 /// </summary>
408 /// <remarks>
409 /// Workaround for DYNPROXY-14 bug. See: http://support.castleproject.org/browse/DYNPROXY-14
410 /// </remarks>
411 private Type GetRealControllerType(Type controllerType)
413 Type prev = controllerType;
415 // try to get the first non-proxy type
416 while(controllerType.Assembly.FullName.StartsWith("DynamicProxyGenAssembly2") ||
417 controllerType.Assembly.FullName.StartsWith("DynamicAssemblyProxyGen"))
419 controllerType = controllerType.BaseType;
421 if ( // controllerType == typeof(SmartDispatcherController) ||
422 controllerType == typeof(IController))
424 // oops, it's a pure-proxy controller. just let it go.
425 controllerType = prev;
426 break;
430 return controllerType;
433 #endregion
435 #region Controller data
437 /// <summary>
438 /// Collects the class level attributes.
439 /// </summary>
440 /// <param name="controllerType">Type of the controller.</param>
441 /// <param name="descriptor">The descriptor.</param>
442 private void CollectClassLevelAttributes(Type controllerType, ControllerMetaDescriptor descriptor)
444 CollectHelpers(descriptor, controllerType);
445 CollectResources(descriptor, controllerType);
446 CollectFilters(descriptor, controllerType);
447 CollectLayout(descriptor, controllerType);
448 CollectRescues(descriptor, controllerType);
449 CollectDefaultAction(descriptor, controllerType);
450 CollectScaffolding(descriptor, controllerType);
451 CollectDynamicAction(descriptor, controllerType);
452 CollectCacheConfigure(descriptor, controllerType);
455 /// <summary>
456 /// Collects the default action.
457 /// </summary>
458 /// <param name="descriptor">The descriptor.</param>
459 /// <param name="controllerType">Type of the controller.</param>
460 private void CollectDefaultAction(ControllerMetaDescriptor descriptor, Type controllerType)
462 object[] attributes = controllerType.GetCustomAttributes(typeof(DefaultActionAttribute), true);
464 if (attributes.Length != 0)
466 descriptor.DefaultAction = (DefaultActionAttribute) attributes[0];
470 /// <summary>
471 /// Collects the scaffolding.
472 /// </summary>
473 /// <param name="descriptor">The descriptor.</param>
474 /// <param name="controllerType">Type of the controller.</param>
475 private void CollectScaffolding(ControllerMetaDescriptor descriptor, Type controllerType)
477 object[] attributes = controllerType.GetCustomAttributes(typeof(ScaffoldingAttribute), false);
479 if (attributes.Length != 0)
481 foreach(ScaffoldingAttribute scaffolding in attributes)
483 descriptor.Scaffoldings.Add(scaffolding);
488 /// <summary>
489 /// Collects the dynamic action.
490 /// </summary>
491 /// <param name="descriptor">The descriptor.</param>
492 /// <param name="controllerType">Type of the controller.</param>
493 private void CollectDynamicAction(ControllerMetaDescriptor descriptor, Type controllerType)
495 object[] attributes = controllerType.GetCustomAttributes(typeof(DynamicActionProviderAttribute), true);
497 if (attributes.Length != 0)
499 foreach(DynamicActionProviderAttribute attr in attributes)
501 descriptor.ActionProviders.Add(attr.ProviderType);
506 /// <summary>
507 /// Collects the helpers.
508 /// </summary>
509 /// <param name="descriptor">The descriptor.</param>
510 /// <param name="controllerType">Type of the controller.</param>
511 private void CollectHelpers(ControllerMetaDescriptor descriptor, Type controllerType)
513 descriptor.Helpers = helperDescriptorProvider.CollectHelpers(controllerType);
516 /// <summary>
517 /// Collects the filters.
518 /// </summary>
519 /// <param name="descriptor">The descriptor.</param>
520 /// <param name="controllerType">Type of the controller.</param>
521 private void CollectFilters(ControllerMetaDescriptor descriptor, Type controllerType)
523 descriptor.Filters = filterDescriptorProvider.CollectFilters(controllerType);
525 Array.Sort(descriptor.Filters, FilterDescriptorComparer.Instance);
528 /// <summary>
529 /// Collects the layout.
530 /// </summary>
531 /// <param name="descriptor">The descriptor.</param>
532 /// <param name="memberInfo">The member info.</param>
533 private void CollectLayout(BaseMetaDescriptor descriptor, MemberInfo memberInfo)
535 descriptor.Layout = layoutDescriptorProvider.CollectLayout(memberInfo);
538 /// <summary>
539 /// Collects the rescues.
540 /// </summary>
541 /// <param name="descriptor">The descriptor.</param>
542 /// <param name="memberInfo">The member info.</param>
543 private void CollectRescues(BaseMetaDescriptor descriptor, MethodInfo memberInfo)
545 descriptor.Rescues = rescueDescriptorProvider.CollectRescues(memberInfo);
548 /// <summary>
549 /// Collects the rescues.
550 /// </summary>
551 /// <param name="descriptor">The descriptor.</param>
552 /// <param name="type">The type.</param>
553 private void CollectRescues(BaseMetaDescriptor descriptor, Type type)
555 descriptor.Rescues = rescueDescriptorProvider.CollectRescues(type);
558 /// <summary>
559 /// Collects the cache configures.
560 /// </summary>
561 /// <param name="descriptor">The descriptor.</param>
562 /// <param name="memberInfo">The member info.</param>
563 private void CollectCacheConfigure(BaseMetaDescriptor descriptor, MemberInfo memberInfo)
565 object[] configurers = memberInfo.GetCustomAttributes(typeof(ICachePolicyConfigurer), true);
567 if (configurers.Length != 0)
569 descriptor.CacheConfigurer = (ICachePolicyConfigurer) configurers[0];
573 #endregion
575 private ControllerMetaDescriptor CreateMetaDescriptor()
577 if (Create != null)
579 return Create();
581 else
583 return new ControllerMetaDescriptor();
587 private ActionMetaDescriptor CreateActionDescriptor()
589 if (ActionCreate != null)
591 return ActionCreate();
593 else
595 return new ActionMetaDescriptor();
599 /// <summary>
600 /// This <see cref="IComparer"/> implementation
601 /// is used to sort the filters based on their Execution Order.
602 /// </summary>
603 private class FilterDescriptorComparer : IComparer
605 private static readonly FilterDescriptorComparer instance = new FilterDescriptorComparer();
607 /// <summary>
608 /// Initializes a new instance of the <see cref="FilterDescriptorComparer"/> class.
609 /// </summary>
610 private FilterDescriptorComparer()
614 /// <summary>
615 /// Gets the instance.
616 /// </summary>
617 /// <value>The instance.</value>
618 public static FilterDescriptorComparer Instance
620 get { return instance; }
623 /// <summary>
624 /// Compares the specified left.
625 /// </summary>
626 /// <param name="left">The left.</param>
627 /// <param name="right">The right.</param>
628 /// <returns></returns>
629 public int Compare(object left, object right)
631 return ((FilterDescriptor) left).ExecutionOrder - ((FilterDescriptor) right).ExecutionOrder;
635 /// <summary>
636 /// This <see cref="IComparer"/> implementation
637 /// is used to sort the transformfilters based on their Execution Order.
638 /// </summary>
639 private class TransformFilterDescriptorComparer : IComparer
641 private static readonly TransformFilterDescriptorComparer instance = new TransformFilterDescriptorComparer();
643 /// <summary>
644 /// Initializes a new instance of the <see cref="TransformFilterDescriptorComparer"/> class.
645 /// </summary>
646 private TransformFilterDescriptorComparer()
650 /// <summary>
651 /// Gets the instance.
652 /// </summary>
653 /// <value>The instance.</value>
654 public static TransformFilterDescriptorComparer Instance
656 get { return instance; }
659 /// <summary>
660 /// Compares the specified left.
661 /// </summary>
662 /// <param name="left">The left.</param>
663 /// <param name="right">The right.</param>
664 /// <returns></returns>
665 public int Compare(object left, object right)
667 return ((TransformFilterDescriptor) right).ExecutionOrder - ((TransformFilterDescriptor) left).ExecutionOrder;