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 using NVelocity
.Runtime
.Parser
.Node
;
16 using NVelocity
.Runtime
.Resource
;
17 using NVelocity
.Runtime
.Directive
;
18 using NVelocity
.Runtime
;
19 using NVelocity
.Exception
;
20 using NVelocity
.Runtime
.Parser
;
21 using NVelocity
.App
.Events
;
22 using NVelocity
.Context
;
23 using NVelocity
.Util
.Introspection
;
26 namespace Castle
.MonoRail
.Framework
.Views
.NVelocity
.CustomDirectives
29 using System
.Collections
;
30 using System
.Collections
.Generic
;
31 using System
.Collections
.Specialized
;
34 using Castle
.MonoRail
.Framework
;
35 using Castle
.MonoRail
.Framework
.Internal
;
42 public abstract class AbstractComponentDirective
: Directive
, IViewRenderer
44 private readonly IViewComponentFactory viewComponentFactory
;
45 private IViewEngine viewEngine
;
48 /// Initializes a new instance of the <see cref="AbstractComponentDirective"/> class.
50 /// <param name="viewComponentFactory">The view component factory.</param>
51 /// <param name="viewEngine">The view engine instance</param>
52 public AbstractComponentDirective(IViewComponentFactory viewComponentFactory
, IViewEngine viewEngine
)
54 this.viewComponentFactory
= viewComponentFactory
;
55 this.viewEngine
= viewEngine
;
58 public override bool Render(IInternalContextAdapter context
, TextWriter writer
, INode node
)
60 IEngineContext railsContext
= MonoRailHttpHandlerFactory
.CurrentEngineContext
;
61 IViewComponentRegistry registry
= railsContext
.Services
.GetService
<IViewComponentFactory
>().Registry
;
62 IViewComponentDescriptorProvider viewDescProvider
=
63 railsContext
.Services
.GetService
<IViewComponentDescriptorProvider
>();
64 ICacheProvider cacheProvider
= railsContext
.Services
.CacheProvider
;
66 INode compNameNode
= node
.GetChild(0);
68 if (compNameNode
== null)
70 String message
= String
.Format("You must specify the component name on the #{0} directive", Name
);
71 throw new ViewComponentException(message
);
74 string componentName
= compNameNode
.FirstToken
.Image
;
76 if (componentName
== null)
78 String message
= String
.Format("Could not obtain component name from the #{0} directive", Name
);
79 throw new ViewComponentException(message
);
82 if (componentName
.StartsWith("$"))
84 String nodeContent
= compNameNode
.Literal
.Trim('"', '\'');
85 SimpleNode inlineNode
= runtimeServices
.Parse(new StringReader(nodeContent
), context
.CurrentTemplateName
, false);
87 inlineNode
.Init(context
, runtimeServices
);
88 componentName
= (string) Evaluate(inlineNode
, context
);
91 IDictionary componentParams
= CreateParameters(context
, node
);
93 Type viewComptype
= registry
.GetViewComponent(componentName
);
95 ViewComponentDescriptor descriptor
= null;
98 if (viewComptype
!= null)
100 descriptor
= viewDescProvider
.Collect(viewComptype
);
103 bool isOutputtingToCache
= false;
104 ViewComponentCacheBag bag
= null;
106 if (descriptor
!= null && descriptor
.IsCacheable
)
108 key
= descriptor
.CacheKeyGenerator
.Create(componentName
, componentParams
, railsContext
);
112 ViewComponentCacheBag cachedContent
= (ViewComponentCacheBag
) cacheProvider
.Get(key
.ToString());
114 if (cachedContent
!= null)
118 foreach(KeyValuePair
<string, object> pair
in cachedContent
.ContextEntries
)
120 context
[pair
.Key
] = pair
.Value
;
125 writer
.Write(cachedContent
.Content
);
130 isOutputtingToCache
= true;
131 bag
= new ViewComponentCacheBag();
135 ViewComponent component
= viewComponentFactory
.Create(componentName
);
137 if (component
== null)
139 throw new MonoRailException("ViewComponentFactory returned a null ViewComponent for " + componentName
+ ". " +
140 "Please investigate the implementation: " + viewComponentFactory
.GetType().FullName
);
143 ASTDirective directiveNode
= (ASTDirective
) node
;
144 IViewRenderer renderer
= (IViewRenderer
) directiveNode
.Directive
;
146 NVelocityViewContextAdapter contextAdapter
= new NVelocityViewContextAdapter(componentName
, node
, viewEngine
, renderer
);
147 contextAdapter
.Context
= isOutputtingToCache
? new CacheAwareContext(context
, bag
) : context
;
149 ProcessSubSections(component
, contextAdapter
);
151 INode bodyNode
= null;
153 if (node
.ChildrenCount
> 0)
155 bodyNode
= node
.GetChild(node
.ChildrenCount
- 1);
158 TextWriter output
= isOutputtingToCache
? bag
.CacheWriter
: writer
;
160 contextAdapter
.BodyNode
= bodyNode
;
161 contextAdapter
.ComponentParams
= componentParams
;
162 contextAdapter
.TextWriter
= output
;
164 const string ViewComponentContextKey
= "viewcomponent";
168 context
[ViewComponentContextKey
] = component
;
170 component
.Init(railsContext
, contextAdapter
);
174 if (contextAdapter
.ViewToRender
!= null)
176 RenderComponentView(context
, contextAdapter
.ViewToRender
, output
, contextAdapter
);
179 if (isOutputtingToCache
)
183 cacheProvider
.Store(key
.ToString(), bag
);
185 // Output to correct writer
187 writer
.Write(bag
.Content
);
192 context
.Remove(ViewComponentContextKey
);
198 public bool RenderComponentView(IInternalContextAdapter context
,
199 String viewToRender
, TextWriter writer
,
200 NVelocityViewContextAdapter contextAdapter
)
202 viewToRender
= viewToRender
+ NVelocityViewEngine
.TemplateExtension
;
204 CheckTemplateStack(context
);
206 String encoding
= ExtractEncoding(context
);
208 Template template
= GetTemplate(viewToRender
, encoding
);
210 return RenderView(context
, viewToRender
, template
, writer
);
213 protected virtual void ProcessSubSections(ViewComponent component
, NVelocityViewContextAdapter contextAdapter
)
217 protected virtual IDictionary
CreateParameters(IInternalContextAdapter context
, INode node
)
219 int childrenCount
= node
.ChildrenCount
;
221 if (childrenCount
> 1)
223 INode lastNode
= node
.GetChild(childrenCount
- 1);
225 if (lastNode
.Type
== ParserTreeConstants
.BLOCK
)
230 if (childrenCount
> 1)
232 IDictionary dict
= ProcessFirstParam(node
, context
, childrenCount
);
238 else if (childrenCount
> 2)
240 return ProcessRemainingParams(childrenCount
, node
, context
);
245 return new Hashtable(0);
248 // protected string ComponentName
250 // get { return componentName; }
253 // protected ViewComponent Component
255 // get { return component; }
258 // protected NVelocityViewContextAdapter ContextAdapter
260 // get { return contextAdapter; }
263 private IDictionary
ProcessRemainingParams(int childrenCount
, INode node
, IInternalContextAdapter context
)
265 IDictionary entries
= new HybridDictionary(true);
267 for(int i
= 2; i
< childrenCount
; i
++)
269 INode paramNode
= node
.GetChild(i
);
271 string nodeContent
= paramNode
.Literal
.TrimStart('"', '\'').TrimEnd('"', '\'');
273 string[] parts
= nodeContent
.Split(new char[] {'='}
, 2, StringSplitOptions
.RemoveEmptyEntries
);
275 if (parts
.Length
== 2 && parts
[1].IndexOf("$") != -1)
277 SimpleNode inlineNode
= runtimeServices
.Parse(new StringReader(parts
[1]), context
.CurrentTemplateName
, false);
279 inlineNode
.Init(context
, runtimeServices
);
281 entries
[parts
[0]] = Evaluate(inlineNode
, context
);
283 else if (parts
.Length
== 2)
285 entries
[parts
[0]] = parts
[1];
289 entries
[parts
[0]] = String
.Empty
;
296 private object Evaluate(SimpleNode inlineNode
, IInternalContextAdapter context
)
298 ArrayList values
= new ArrayList();
300 for(int i
= 0; i
< inlineNode
.ChildrenCount
; i
++)
302 INode node
= inlineNode
.GetChild(i
);
304 if (node
.Type
== ParserTreeConstants
.TEXT
)
306 values
.Add(((ASTText
) node
).Text
);
310 values
.Add(node
.Value(context
));
314 if (values
.Count
== 0)
318 else if (values
.Count
== 1)
324 StringBuilder sb
= new StringBuilder();
325 foreach(object value in values
)
329 return sb
.ToString();
334 /// Processes the first param.
335 /// first param can either be the literal string 'with' which means the user
336 /// is using the syntax #blockcomponent(ComponentName with "param1=value1" "param2=value2")
337 /// or it could be a dictionary string like:
338 /// #blockcomponent(ComponentName "#{ param1='value1', param2='value2' }")
339 /// anything different than that will throw an exception
341 /// <param name="node">The node.</param>
342 /// <param name="context">The context.</param>
343 /// <param name="childrenCount">The children count.</param>
344 /// <returns></returns>
345 private IDictionary
ProcessFirstParam(INode node
, IInternalContextAdapter context
, int childrenCount
)
347 INode compNameNode
= node
.GetChild(0);
348 string componentName
= compNameNode
.FirstToken
.Image
;
350 INode withNode
= node
.GetChild(1);
352 String withName
= withNode
.FirstToken
.Image
;
354 if (!"with".Equals(withName
))
356 IDictionary dict
= withNode
.Value(context
) as IDictionary
;
360 if (childrenCount
> 2)
362 String message
= String
.Format("A #{0} directive with a dictionary " +
363 "string parameter cannot have extra params - component {0}", componentName
);
364 throw new ViewComponentException(message
);
370 String message
= String
.Format("A #{0} directive with parameters must use " +
371 "the keyword 'with' - component {0}", componentName
);
372 throw new ViewComponentException(message
);
379 private bool RenderView(IInternalContextAdapter context
,
380 String viewToRender
, Template template
, TextWriter writer
)
384 context
.PushCurrentTemplateName(viewToRender
);
385 ((SimpleNode
) template
.Data
).Render(context
, writer
);
389 context
.PopCurrentTemplateName();
395 private Template
GetTemplate(String viewToRender
, String encoding
)
397 return runtimeServices
.GetTemplate(viewToRender
, encoding
);
400 private String
ExtractEncoding(IInternalContextAdapter context
)
402 Resource current
= context
.CurrentResource
;
408 encoding
= current
.Encoding
;
412 encoding
= (String
) runtimeServices
.GetProperty(RuntimeConstants
.INPUT_ENCODING
);
418 private void CheckTemplateStack(IInternalContextAdapter context
)
420 Object
[] templateStack
= context
.TemplateNameStack
;
422 if (templateStack
.Length
>= runtimeServices
.GetInt(RuntimeConstants
.PARSE_DIRECTIVE_MAXDEPTH
, 20))
424 StringBuilder path
= new StringBuilder();
426 for(int i
= 0; i
< templateStack
.Length
; ++i
)
428 path
.Append(" > " + templateStack
[i
]);
431 throw new Exception("Max recursion depth reached (" + templateStack
.Length
+ ")" + " File stack:" + path
);
436 public class CacheAwareContext
: IInternalContextAdapter
438 private readonly IInternalContextAdapter context
;
439 private readonly ViewComponentCacheBag bag
;
441 public CacheAwareContext(IInternalContextAdapter context
, ViewComponentCacheBag bag
)
443 this.context
= context
;
447 object IInternalContextAdapter
.Remove(object key
)
449 if (bag
.ContextEntries
.ContainsKey(key
.ToString()))
451 bag
.ContextEntries
.Remove(key
.ToString());
453 return context
.Remove(key
);
456 public void PushCurrentTemplateName(string s
)
458 context
.PushCurrentTemplateName(s
);
461 public void PopCurrentTemplateName()
463 context
.PopCurrentTemplateName();
466 public IntrospectionCacheData
ICacheGet(object key
)
468 return context
.ICacheGet(key
);
471 public void ICachePut(object key
, IntrospectionCacheData o
)
473 context
.ICachePut(key
, o
);
476 public string CurrentTemplateName
478 get { return context.CurrentTemplateName; }
481 public object[] TemplateNameStack
483 get { return context.TemplateNameStack; }
486 public Resource CurrentResource
488 get { return context.CurrentResource; }
489 set { context.CurrentResource = value; }
492 public object Put(string key
, object value)
494 bag
.ContextEntries
[key
] = value;
496 return context
.Put(key
, value);
499 public object Get(string key
)
501 return context
.Get(key
);
504 public bool ContainsKey(object key
)
506 return context
.ContainsKey(key
);
509 object IContext
.Remove(object key
)
511 if (bag
.ContextEntries
.ContainsKey(key
.ToString()))
513 bag
.ContextEntries
.Remove(key
.ToString());
516 return context
.Remove(key
);
519 public IContext InternalUserContext
521 get { return context.InternalUserContext; }
524 public IInternalContextAdapter BaseContext
526 get { return context.BaseContext; }
529 public EventCartridge
AttachEventCartridge(EventCartridge ec
)
531 return context
.AttachEventCartridge(ec
);
534 public EventCartridge EventCartridge
536 get { return context.EventCartridge; }
539 public bool Contains(object key
)
541 return context
.Contains(key
);
544 public void Add(object key
, object value)
546 bag
.ContextEntries
[key
.ToString()] = value;
547 context
.Add(key
, value);
555 IDictionaryEnumerator IDictionary
.GetEnumerator()
557 return context
.GetEnumerator();
560 public void Remove(object key
)
562 if (bag
.ContextEntries
.ContainsKey(key
.ToString()))
564 bag
.ContextEntries
.Remove(key
.ToString());
567 ((IDictionary
) context
).Remove(key
);
570 public object this[object key
]
572 get { return context[key]; }
575 bag
.ContextEntries
[key
.ToString()] = value;
576 context
[key
] = value;
580 public ICollection Keys
582 get { return ((IDictionary) context).Keys; }
585 object[] IContext
.Keys
587 get { return ((IContext) context).Keys; }
590 public ICollection Values
592 get { return context.Values; }
595 public bool IsReadOnly
597 get { return context.IsReadOnly; }
600 public bool IsFixedSize
602 get { return context.IsFixedSize; }
605 public void CopyTo(Array array
, int index
)
607 context
.CopyTo(array
, index
);
612 get { return ((ICollection) context).Count; }
615 public object SyncRoot
617 get { return context.SyncRoot; }
620 public bool IsSynchronized
622 get { return context.IsSynchronized; }
625 public IEnumerator
GetEnumerator()
627 return ((IEnumerable
) context
).GetEnumerator();