1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
19 using System
.Reflection
;
20 using System
.Threading
;
21 using Castle
.Core
.Logging
;
22 using Castle
.MonoRail
.Framework
.Services
.Utils
;
27 /// Constructs and caches all collected information
28 /// about a <see cref="IController"/> and its actions.
29 /// <seealso cref="ControllerMetaDescriptor"/>
31 public class DefaultControllerDescriptorProvider
: IControllerDescriptorProvider
34 /// The logger instance
36 private ILogger logger
= NullLogger
.Instance
;
39 /// Used to lock the cache
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
;
50 private IReturnBinderDescriptorProvider returnBinderDescriptorProvider
;
53 /// Initializes a new instance of the <see cref="DefaultControllerDescriptorProvider"/> class.
55 public DefaultControllerDescriptorProvider()
60 /// Initializes a new instance of the <see cref="DefaultControllerDescriptorProvider"/> class.
62 /// <param name="helperDescriptorProvider">The helper descriptor provider.</param>
63 /// <param name="filterDescriptorProvider">The filter descriptor provider.</param>
64 /// <param name="layoutDescriptorProvider">The layout descriptor provider.</param>
65 /// <param name="rescueDescriptorProvider">The rescue descriptor provider.</param>
66 /// <param name="resourceDescriptorProvider">The resource descriptor provider.</param>
67 /// <param name="transformFilterDescriptorProvider">The transform filter descriptor provider.</param>
68 /// <param name="returnBinderDescriptorProvider">The return binder descriptor provider.</param>
69 public DefaultControllerDescriptorProvider(IHelperDescriptorProvider helperDescriptorProvider
,
70 IFilterDescriptorProvider filterDescriptorProvider
,
71 ILayoutDescriptorProvider layoutDescriptorProvider
,
72 IRescueDescriptorProvider rescueDescriptorProvider
,
73 IResourceDescriptorProvider resourceDescriptorProvider
,
74 ITransformFilterDescriptorProvider transformFilterDescriptorProvider
,
75 IReturnBinderDescriptorProvider returnBinderDescriptorProvider
)
77 this.helperDescriptorProvider
= helperDescriptorProvider
;
78 this.filterDescriptorProvider
= filterDescriptorProvider
;
79 this.layoutDescriptorProvider
= layoutDescriptorProvider
;
80 this.rescueDescriptorProvider
= rescueDescriptorProvider
;
81 this.resourceDescriptorProvider
= resourceDescriptorProvider
;
82 this.transformFilterDescriptorProvider
= transformFilterDescriptorProvider
;
83 this.returnBinderDescriptorProvider
= returnBinderDescriptorProvider
;
86 #region IMRServiceEnabled implementation
89 /// Services the specified service provider.
91 /// <param name="serviceProvider">The service provider.</param>
92 public void Service(IMonoRailServices serviceProvider
)
94 ILoggerFactory loggerFactory
= (ILoggerFactory
) serviceProvider
.GetService(typeof(ILoggerFactory
));
96 if (loggerFactory
!= null)
98 logger
= loggerFactory
.Create(typeof(DefaultControllerDescriptorProvider
));
101 helperDescriptorProvider
= serviceProvider
.GetService
<IHelperDescriptorProvider
>();
102 filterDescriptorProvider
= serviceProvider
.GetService
<IFilterDescriptorProvider
>();
103 layoutDescriptorProvider
= serviceProvider
.GetService
<ILayoutDescriptorProvider
>();
104 rescueDescriptorProvider
= serviceProvider
.GetService
<IRescueDescriptorProvider
>();
105 resourceDescriptorProvider
= serviceProvider
.GetService
<IResourceDescriptorProvider
>();
106 transformFilterDescriptorProvider
= serviceProvider
.GetService
<ITransformFilterDescriptorProvider
>();
107 returnBinderDescriptorProvider
= serviceProvider
.GetService
<IReturnBinderDescriptorProvider
>();
113 /// Occurs when the providers needs to create a <see cref="ControllerMetaDescriptor"/>.
115 public event MetaCreatorHandler Create
;
118 /// Occurs when the meta descriptor is about to the returned to the caller.
120 public event ControllerMetaDescriptorHandler AfterProcess
;
123 /// Occurs when the providers needs to create a <see cref="ActionMetaDescriptor"/>.
125 public event ActionMetaCreatorHandler ActionCreate
;
128 /// Occurs when the meta descriptor is about to be included on the <see cref="ControllerMetaDescriptor"/>.
130 public event ActionMetaDescriptorHandler AfterActionProcess
;
133 /// Constructs and populates a <see cref="ControllerMetaDescriptor"/>.
136 /// This implementation is also responsible for caching
137 /// constructed meta descriptors.
139 public ControllerMetaDescriptor
BuildDescriptor(IController controller
)
141 Type controllerType
= controller
.GetType();
143 return BuildDescriptor(controllerType
);
147 /// Constructs and populates a <see cref="ControllerMetaDescriptor"/>.
150 /// This implementation is also responsible for caching
151 /// constructed meta descriptors.
153 public ControllerMetaDescriptor
BuildDescriptor(Type controllerType
)
155 ControllerMetaDescriptor desc
;
157 locker
.AcquireReaderLock(-1);
160 desc
= (ControllerMetaDescriptor
) descriptorRepository
[controllerType
];
169 locker
.ReleaseReaderLock();
174 locker
.UpgradeToWriterLock(-1);
176 // We need to recheck after getting the writer lock
177 desc
= (ControllerMetaDescriptor
) descriptorRepository
[controllerType
];
184 desc
= InternalBuildDescriptor(controllerType
);
186 descriptorRepository
[controllerType
] = desc
;
190 locker
.ReleaseWriterLock();
197 /// Builds the <see cref="ControllerMetaDescriptor"/> for the specified controller type
199 /// <param name="controllerType">Type of the controller.</param>
200 /// <returns></returns>
201 private ControllerMetaDescriptor
InternalBuildDescriptor(Type controllerType
)
203 if (logger
.IsDebugEnabled
)
205 logger
.DebugFormat("Building controller descriptor for {0}", controllerType
);
208 ControllerMetaDescriptor descriptor
= CreateMetaDescriptor();
210 descriptor
.ControllerDescriptor
= ControllerInspectionUtil
.Inspect(controllerType
);
212 CollectClassLevelAttributes(controllerType
, descriptor
);
214 CollectActions(controllerType
, descriptor
);
216 CollectActionLevelAttributes(descriptor
);
218 if (AfterProcess
!= null)
220 AfterProcess(descriptor
);
229 /// Collects the actions.
231 /// <param name="controllerType">Type of the controller.</param>
232 /// <param name="desc">The desc.</param>
233 private void CollectActions(Type controllerType
, ControllerMetaDescriptor desc
)
235 // HACK: GetRealControllerType is a workaround for DYNPROXY-14 bug
236 // see: http://support.castleproject.org/browse/DYNPROXY-14
237 controllerType
= GetRealControllerType(controllerType
);
239 MethodInfo
[] methods
= controllerType
.GetMethods(BindingFlags
.Public
| BindingFlags
.Instance
);
241 foreach(MethodInfo method
in methods
)
243 Type declaringType
= method
.DeclaringType
;
245 if (method
.IsSpecialName
)
250 if (declaringType
== typeof(Object
) ||
251 declaringType
== typeof(IController
) || declaringType
== typeof(Controller
))
252 // || declaringType == typeof(SmartDispatcherController))
257 if (desc
.Actions
.Contains(method
.Name
))
259 ArrayList list
= desc
.Actions
[method
.Name
] as ArrayList
;
263 list
= new ArrayList();
264 list
.Add(desc
.Actions
[method
.Name
]);
266 desc
.Actions
[method
.Name
] = list
;
273 desc
.Actions
[method
.Name
] = method
;
277 MergeAsyncMethodPairsToSingleAction(desc
);
280 private void MergeAsyncMethodPairsToSingleAction(ControllerMetaDescriptor desc
)
282 foreach(string name
in new ArrayList(desc
.Actions
.Keys
))
284 // skip methods that are not named "BeginXyz", including "Begin"
285 if (name
.StartsWith("Begin", StringComparison
.InvariantCultureIgnoreCase
) == false || name
.Length
== 5)
290 string actionName
= name
.Substring("Begin".Length
);
292 ArrayList list
= desc
.Actions
[name
] as ArrayList
;
295 foreach(MethodInfo info
in list
)
297 if (info
.ReturnType
== typeof(IAsyncResult
))
299 throw new MonoRailException("Action '" + actionName
+ "' on controller '" + desc
.ControllerDescriptor
.Name
+
300 "' is an async action, but there are method overloads '" + name
+
301 "(...)', which is not allowed on async actions.");
307 if (desc
.Actions
.Contains(actionName
))
309 throw new MonoRailException("Found both async method '" + name
+ "' and sync method '" + actionName
+
310 "' on controller '" + desc
.ControllerDescriptor
.Name
+
311 "'. MonoRail doesn't support mixing sync and async methods for the same action");
314 string endActionName
= "End" + actionName
;
315 if (desc
.Actions
.Contains(endActionName
) == false)
317 throw new MonoRailException("Found beginning of async pair '" + name
+ "' but not the end '" + endActionName
+
318 "' on controller '" + desc
.ControllerDescriptor
.Name
+ "', did you forget to define " +
319 endActionName
+ "(IAsyncResult ar) ?");
322 if (desc
.Actions
[endActionName
] is IList
)
324 throw new MonoRailException("Found more than a single " + endActionName
+
325 " method, for async action '" + actionName
+ "' on controller '" +
326 desc
.ControllerDescriptor
.Name
+ "', only a single " +
327 endActionName
+ " may be defined as part of an async action");
330 MethodInfo beginActionInfo
= (MethodInfo
) desc
.Actions
[name
];
331 MethodInfo endActionInfo
= (MethodInfo
) desc
.Actions
[endActionName
];
333 desc
.Actions
.Remove(name
);
334 desc
.Actions
.Remove(endActionName
);
335 desc
.Actions
[actionName
] = new AsyncActionPair(actionName
, beginActionInfo
, endActionInfo
);
340 /// Collects the action level attributes.
342 /// <param name="descriptor">The descriptor.</param>
343 private void CollectActionLevelAttributes(ControllerMetaDescriptor descriptor
)
345 foreach(object action
in descriptor
.Actions
.Values
)
349 foreach(MethodInfo overloadedAction
in (action
as IList
))
351 CollectActionAttributes(overloadedAction
, descriptor
);
357 MethodInfo methodInfo
= action
as MethodInfo
;
358 if (methodInfo
!= null)
360 CollectActionAttributes(methodInfo
, descriptor
);
364 AsyncActionPair asyncActionPair
= (AsyncActionPair
) action
;
365 CollectActionAttributes(asyncActionPair
.BeginActionInfo
, descriptor
);
366 CollectActionAttributes(asyncActionPair
.EndActionInfo
, descriptor
);
372 /// Collects the action attributes.
374 /// <param name="method">The method.</param>
375 /// <param name="descriptor">The descriptor.</param>
376 private void CollectActionAttributes(MethodInfo method
, ControllerMetaDescriptor descriptor
)
378 if (logger
.IsDebugEnabled
)
380 logger
.DebugFormat("Collection attributes for action {0}", method
.Name
);
383 ActionMetaDescriptor actionDescriptor
= descriptor
.GetAction(method
);
385 if (actionDescriptor
== null)
387 actionDescriptor
= CreateActionDescriptor();
389 descriptor
.AddAction(method
, actionDescriptor
);
392 CollectResources(actionDescriptor
, method
);
393 CollectSkipFilter(actionDescriptor
, method
);
394 CollectRescues(actionDescriptor
, method
);
395 CollectAccessibleThrough(actionDescriptor
, method
);
396 CollectSkipRescue(actionDescriptor
, method
);
397 CollectLayout(actionDescriptor
, method
);
398 CollectCacheConfigure(actionDescriptor
, method
);
399 CollectTransformFilter(actionDescriptor
, method
);
400 CollectReturnTypeBinder(actionDescriptor
, method
);
402 if (method
.IsDefined(typeof(AjaxActionAttribute
), true))
404 descriptor
.AjaxActions
.Add(method
);
407 if (method
.IsDefined(typeof(DefaultActionAttribute
), true))
409 if (descriptor
.DefaultAction
!= null)
411 throw new MonoRailException(
412 "Cannot resolve a default action for {0}, DefaultActionAttribute was declared more than once.",
413 method
.DeclaringType
.FullName
);
415 descriptor
.DefaultAction
= new DefaultActionAttribute(method
.Name
);
418 if (AfterActionProcess
!= null)
420 AfterActionProcess(actionDescriptor
);
425 /// Collects the skip rescue.
427 /// <param name="actionDescriptor">The action descriptor.</param>
428 /// <param name="method">The method.</param>
429 private void CollectSkipRescue(ActionMetaDescriptor actionDescriptor
, MethodInfo method
)
431 object[] attributes
= method
.GetCustomAttributes(typeof(SkipRescueAttribute
), true);
433 if (attributes
.Length
!= 0)
435 actionDescriptor
.SkipRescue
= (SkipRescueAttribute
) attributes
[0];
440 /// Collects the accessible through.
442 /// <param name="actionDescriptor">The action descriptor.</param>
443 /// <param name="method">The method.</param>
444 private void CollectAccessibleThrough(ActionMetaDescriptor actionDescriptor
, MethodInfo method
)
446 object[] attributes
= method
.GetCustomAttributes(typeof(AccessibleThroughAttribute
), true);
448 if (attributes
.Length
!= 0)
450 actionDescriptor
.AccessibleThrough
= (AccessibleThroughAttribute
) attributes
[0];
455 /// Collects the skip filter.
457 /// <param name="actionDescriptor">The action descriptor.</param>
458 /// <param name="method">The method.</param>
459 private void CollectSkipFilter(ActionMetaDescriptor actionDescriptor
, MethodInfo method
)
461 object[] attributes
= method
.GetCustomAttributes(typeof(SkipFilterAttribute
), true);
463 foreach(SkipFilterAttribute attr
in attributes
)
465 actionDescriptor
.SkipFilters
.Add(attr
);
470 /// Collects the resources.
472 /// <param name="desc">The desc.</param>
473 /// <param name="memberInfo">The member info.</param>
474 private void CollectResources(BaseMetaDescriptor desc
, MemberInfo memberInfo
)
476 desc
.Resources
= resourceDescriptorProvider
.CollectResources(memberInfo
);
480 /// Collects the transform filter.
482 /// <param name="actionDescriptor">The action descriptor.</param>
483 /// <param name="method">The method.</param>
484 private void CollectTransformFilter(ActionMetaDescriptor actionDescriptor
, MethodInfo method
)
486 actionDescriptor
.TransformFilters
= transformFilterDescriptorProvider
.CollectFilters((method
));
487 Array
.Sort(actionDescriptor
.TransformFilters
, TransformFilterDescriptorComparer
.Instance
);
491 /// Collects the return type binder.
493 /// <param name="actionDescriptor">The action descriptor.</param>
494 /// <param name="method">The method.</param>
495 private void CollectReturnTypeBinder(ActionMetaDescriptor actionDescriptor
, MethodInfo method
)
497 actionDescriptor
.ReturnDescriptor
= returnBinderDescriptorProvider
.Collect(method
);
501 /// Gets the real controller type, instead of the proxy type.
504 /// Workaround for DYNPROXY-14 bug. See: http://support.castleproject.org/browse/DYNPROXY-14
506 private Type
GetRealControllerType(Type controllerType
)
508 Type prev
= controllerType
;
510 // try to get the first non-proxy type
511 while(controllerType
.Assembly
.FullName
.StartsWith("DynamicProxyGenAssembly2") ||
512 controllerType
.Assembly
.FullName
.StartsWith("DynamicAssemblyProxyGen"))
514 controllerType
= controllerType
.BaseType
;
516 if ( // controllerType == typeof(SmartDispatcherController) ||
517 controllerType
== typeof(IController
))
519 // oops, it's a pure-proxy controller. just let it go.
520 controllerType
= prev
;
525 return controllerType
;
530 #region Controller data
533 /// Collects the class level attributes.
535 /// <param name="controllerType">Type of the controller.</param>
536 /// <param name="descriptor">The descriptor.</param>
537 private void CollectClassLevelAttributes(Type controllerType
, ControllerMetaDescriptor descriptor
)
539 CollectHelpers(descriptor
, controllerType
);
540 CollectResources(descriptor
, controllerType
);
541 CollectFilters(descriptor
, controllerType
);
542 CollectLayout(descriptor
, controllerType
);
543 CollectRescues(descriptor
, controllerType
);
544 CollectDefaultAction(descriptor
, controllerType
);
545 CollectScaffolding(descriptor
, controllerType
);
546 CollectDynamicAction(descriptor
, controllerType
);
547 CollectCacheConfigure(descriptor
, controllerType
);
551 /// Collects the default action.
553 /// <param name="descriptor">The descriptor.</param>
554 /// <param name="controllerType">Type of the controller.</param>
555 private void CollectDefaultAction(ControllerMetaDescriptor descriptor
, Type controllerType
)
557 object[] attributes
= controllerType
.GetCustomAttributes(typeof(DefaultActionAttribute
), true);
559 if (attributes
.Length
!= 0)
561 descriptor
.DefaultAction
= (DefaultActionAttribute
) attributes
[0];
566 /// Collects the scaffolding.
568 /// <param name="descriptor">The descriptor.</param>
569 /// <param name="controllerType">Type of the controller.</param>
570 private void CollectScaffolding(ControllerMetaDescriptor descriptor
, Type controllerType
)
572 object[] attributes
= controllerType
.GetCustomAttributes(typeof(ScaffoldingAttribute
), false);
574 if (attributes
.Length
!= 0)
576 foreach(ScaffoldingAttribute scaffolding
in attributes
)
578 descriptor
.Scaffoldings
.Add(scaffolding
);
584 /// Collects the dynamic action.
586 /// <param name="descriptor">The descriptor.</param>
587 /// <param name="controllerType">Type of the controller.</param>
588 private void CollectDynamicAction(ControllerMetaDescriptor descriptor
, Type controllerType
)
590 object[] attributes
= controllerType
.GetCustomAttributes(typeof(DynamicActionProviderAttribute
), true);
592 if (attributes
.Length
!= 0)
594 foreach(DynamicActionProviderAttribute attr
in attributes
)
596 descriptor
.ActionProviders
.Add(attr
.ProviderType
);
602 /// Collects the helpers.
604 /// <param name="descriptor">The descriptor.</param>
605 /// <param name="controllerType">Type of the controller.</param>
606 private void CollectHelpers(ControllerMetaDescriptor descriptor
, Type controllerType
)
608 descriptor
.Helpers
= helperDescriptorProvider
.CollectHelpers(controllerType
);
612 /// Collects the filters.
614 /// <param name="descriptor">The descriptor.</param>
615 /// <param name="controllerType">Type of the controller.</param>
616 private void CollectFilters(ControllerMetaDescriptor descriptor
, Type controllerType
)
618 descriptor
.Filters
= filterDescriptorProvider
.CollectFilters(controllerType
);
620 Array
.Sort(descriptor
.Filters
, FilterDescriptorComparer
.Instance
);
624 /// Collects the layout.
626 /// <param name="descriptor">The descriptor.</param>
627 /// <param name="memberInfo">The member info.</param>
628 private void CollectLayout(BaseMetaDescriptor descriptor
, MemberInfo memberInfo
)
630 descriptor
.Layout
= layoutDescriptorProvider
.CollectLayout(memberInfo
);
634 /// Collects the rescues.
636 /// <param name="descriptor">The descriptor.</param>
637 /// <param name="memberInfo">The member info.</param>
638 private void CollectRescues(BaseMetaDescriptor descriptor
, MethodInfo memberInfo
)
640 descriptor
.Rescues
= rescueDescriptorProvider
.CollectRescues(memberInfo
);
644 /// Collects the rescues.
646 /// <param name="descriptor">The descriptor.</param>
647 /// <param name="type">The type.</param>
648 private void CollectRescues(BaseMetaDescriptor descriptor
, Type type
)
650 descriptor
.Rescues
= rescueDescriptorProvider
.CollectRescues(type
);
654 /// Collects the cache configures.
656 /// <param name="descriptor">The descriptor.</param>
657 /// <param name="memberInfo">The member info.</param>
658 private void CollectCacheConfigure(BaseMetaDescriptor descriptor
, MemberInfo memberInfo
)
660 object[] configurers
= memberInfo
.GetCustomAttributes(typeof(ICachePolicyConfigurer
), true);
662 if (configurers
.Length
!= 0)
664 descriptor
.CacheConfigurer
= (ICachePolicyConfigurer
) configurers
[0];
670 private ControllerMetaDescriptor
CreateMetaDescriptor()
678 return new ControllerMetaDescriptor();
682 private ActionMetaDescriptor
CreateActionDescriptor()
684 if (ActionCreate
!= null)
686 return ActionCreate();
690 return new ActionMetaDescriptor();
695 /// This <see cref="IComparer"/> implementation
696 /// is used to sort the filters based on their Execution Order.
698 private class FilterDescriptorComparer
: IComparer
700 private static readonly FilterDescriptorComparer instance
= new FilterDescriptorComparer();
703 /// Initializes a new instance of the <see cref="FilterDescriptorComparer"/> class.
705 private FilterDescriptorComparer()
710 /// Gets the instance.
712 /// <value>The instance.</value>
713 public static FilterDescriptorComparer Instance
715 get { return instance; }
719 /// Compares the specified left.
721 /// <param name="left">The left.</param>
722 /// <param name="right">The right.</param>
723 /// <returns></returns>
724 public int Compare(object left
, object right
)
726 return ((FilterDescriptor
) left
).ExecutionOrder
- ((FilterDescriptor
) right
).ExecutionOrder
;
731 /// This <see cref="IComparer"/> implementation
732 /// is used to sort the transformfilters based on their Execution Order.
734 private class TransformFilterDescriptorComparer
: IComparer
736 private static readonly TransformFilterDescriptorComparer instance
= new TransformFilterDescriptorComparer();
739 /// Initializes a new instance of the <see cref="TransformFilterDescriptorComparer"/> class.
741 private TransformFilterDescriptorComparer()
746 /// Gets the instance.
748 /// <value>The instance.</value>
749 public static TransformFilterDescriptorComparer Instance
751 get { return instance; }
755 /// Compares the specified left.
757 /// <param name="left">The left.</param>
758 /// <param name="right">The right.</param>
759 /// <returns></returns>
760 public int Compare(object left
, object right
)
762 return ((TransformFilterDescriptor
) right
).ExecutionOrder
- ((TransformFilterDescriptor
) left
).ExecutionOrder
;