1 // Copyright 2004-2007 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 Template
= NVelocity
.Template
;
23 namespace Castle
.MonoRail
.Framework
.Views
.NVelocity
.CustomDirectives
26 using System
.Collections
;
27 using System
.Collections
.Generic
;
28 using System
.Collections
.Specialized
;
31 using Castle
.MonoRail
.Framework
;
32 using Castle
.MonoRail
.Framework
.Internal
;
33 using global::NVelocity
.App
.Events
;
34 using global::NVelocity
.Context
;
35 using global::NVelocity
.Util
.Introspection
;
40 public abstract class AbstractComponentDirective
: Directive
, IViewRenderer
42 private readonly IViewComponentFactory viewComponentFactory
;
44 private String componentName
;
45 private ViewComponent component
;
46 private NVelocityViewContextAdapter contextAdapter
;
47 private IViewEngine viewEngine
;
48 private INode compNameNode
;
51 /// Initializes a new instance of the <see cref="AbstractComponentDirective"/> class.
53 /// <param name="viewComponentFactory">The view component factory.</param>
54 /// <param name="viewEngine">The view engine instance</param>
55 public AbstractComponentDirective(IViewComponentFactory viewComponentFactory
, IViewEngine viewEngine
)
57 this.viewComponentFactory
= viewComponentFactory
;
58 this.viewEngine
= viewEngine
;
61 public override void Init(IRuntimeServices rs
, IInternalContextAdapter context
, INode node
)
63 base.Init(rs
, context
, node
);
65 compNameNode
= node
.GetChild(0);
67 if (compNameNode
== null)
69 String message
= String
.Format("You must specify the component name on the #{0} directive", Name
);
70 throw new ViewComponentException(message
);
74 public override bool Render(IInternalContextAdapter context
, TextWriter writer
, INode node
)
76 IRailsEngineContext railsContext
= MonoRailHttpHandler
.CurrentContext
;
77 IViewComponentRegistry registry
= railsContext
.GetService
<IViewComponentFactory
>().Registry
;
78 IViewComponentDescriptorProvider viewDescProvider
= railsContext
.GetService
<IViewComponentDescriptorProvider
>();
79 ICacheProvider cacheProvider
= railsContext
.GetService
<ICacheProvider
>();
81 componentName
= compNameNode
.FirstToken
.Image
;
83 if (componentName
== null)
85 String message
= String
.Format("Could not obtain component name from the #{0} directive", Name
);
86 throw new ViewComponentException(message
);
89 if (componentName
.StartsWith("$"))
91 String nodeContent
= compNameNode
.Literal
.Trim('"', '\'');
92 SimpleNode inlineNode
= rsvc
.Parse(new StringReader(nodeContent
), context
.CurrentTemplateName
, false);
94 inlineNode
.Init(context
, rsvc
);
95 componentName
= (String
) Evaluate(inlineNode
, context
);
98 IDictionary componentParams
= CreateParameters(context
, node
);
100 Type viewComptype
= registry
.GetViewComponent(componentName
);
102 ViewComponentDescriptor descriptor
= null;
105 if (viewComptype
!= null)
107 descriptor
= viewDescProvider
.Collect(viewComptype
);
110 bool isOutputtingToCache
= false;
111 ViewComponentCacheBag bag
= null;
113 if (descriptor
!= null && descriptor
.IsCacheable
)
115 key
= descriptor
.CacheKeyGenerator
.Create(componentName
, componentParams
, railsContext
);
119 throw new MonoRailException("CacheKeyGenerator returned a null CacheKey implementation (Not good at all). " +
120 "Please investigate the implementation: " + descriptor
.CacheKeyGenerator
.GetType().FullName
);
123 ViewComponentCacheBag cachedContent
= (ViewComponentCacheBag
) cacheProvider
.Get(key
.ToString());
125 if (cachedContent
!= null)
129 foreach(KeyValuePair
<string, object> pair
in cachedContent
.ContextEntries
)
131 context
[pair
.Key
] = pair
.Value
;
136 writer
.Write(cachedContent
.Content
);
141 isOutputtingToCache
= true;
142 bag
= new ViewComponentCacheBag();
145 component
= viewComponentFactory
.Create(componentName
);
147 if (component
== null)
149 throw new MonoRailException("ViewComponentFactory returned a null ViewComponent for " + componentName
+ ". " +
150 "Please investigate the implementation: " + viewComponentFactory
.GetType().FullName
);
153 ASTDirective directiveNode
= (ASTDirective
) node
;
154 IViewRenderer renderer
= (IViewRenderer
) directiveNode
.Directive
;
156 contextAdapter
= new NVelocityViewContextAdapter(componentName
, node
, viewEngine
, renderer
);
157 contextAdapter
.Context
= isOutputtingToCache
? new CacheAwareContext(context
, bag
) : context
;
159 ProcessSubSections();
161 INode bodyNode
= null;
163 if (node
.ChildrenCount
> 0)
165 bodyNode
= node
.GetChild(node
.ChildrenCount
- 1);
169 TextWriter output
= isOutputtingToCache
? bag
.CacheWriter
: writer
;
171 contextAdapter
.BodyNode
= bodyNode
;
172 contextAdapter
.ComponentParams
= componentParams
;
173 contextAdapter
.TextWriter
= output
;
175 const string ViewComponentContextKey
= "viewcomponent";
179 context
[ViewComponentContextKey
] = component
;
181 component
.Init(railsContext
, contextAdapter
);
185 if (contextAdapter
.ViewToRender
!= null)
187 RenderComponentView(context
, contextAdapter
.ViewToRender
, output
, contextAdapter
);
190 if (isOutputtingToCache
)
194 cacheProvider
.Store(key
.ToString(), bag
);
196 // Output to correct writer
198 writer
.Write(bag
.Content
);
203 context
.Remove(ViewComponentContextKey
);
209 public bool RenderComponentView(IInternalContextAdapter context
, String viewToRender
, TextWriter writer
, NVelocityViewContextAdapter contextAdapter
)
211 viewToRender
= viewToRender
+ NVelocityViewEngine
.TemplateExtension
;
213 CheckTemplateStack(context
);
215 String encoding
= ExtractEncoding(context
);
217 Template template
= GetTemplate(viewToRender
, encoding
);
219 return RenderView(context
, viewToRender
, template
, writer
);
222 protected virtual void ProcessSubSections()
226 protected virtual IDictionary
CreateParameters(IInternalContextAdapter context
, INode node
)
228 int childrenCount
= node
.ChildrenCount
;
230 if (childrenCount
> 1)
232 INode lastNode
= node
.GetChild(childrenCount
- 1);
234 if (lastNode
.Type
== ParserTreeConstants
.BLOCK
)
239 if (childrenCount
> 1)
241 IDictionary dict
= ProcessFirstParam(node
, context
, childrenCount
);
247 else if (childrenCount
> 2)
249 return ProcessRemainingParams(childrenCount
, node
, context
);
254 return new Hashtable(0);
257 protected string ComponentName
259 get { return componentName; }
262 protected ViewComponent Component
264 get { return component; }
267 protected NVelocityViewContextAdapter ContextAdapter
269 get { return contextAdapter; }
272 private IDictionary
ProcessRemainingParams(int childrenCount
, INode node
, IInternalContextAdapter context
)
274 IDictionary entries
= new HybridDictionary(true);
276 for(int i
= 2; i
< childrenCount
; i
++)
278 INode paramNode
= node
.GetChild(i
);
280 string nodeContent
= paramNode
.Literal
.TrimStart('"', '\'').TrimEnd('"', '\'');
282 string[] parts
= nodeContent
.Split(new char[] {'='}
, 2, StringSplitOptions
.RemoveEmptyEntries
);
284 if (parts
.Length
== 2 && parts
[1].IndexOf("$") != -1)
286 SimpleNode inlineNode
= rsvc
.Parse(new StringReader(parts
[1]), context
.CurrentTemplateName
, false);
288 inlineNode
.Init(context
, rsvc
);
290 entries
[parts
[0]] = Evaluate(inlineNode
, context
);
292 else if (parts
.Length
== 2)
294 entries
[parts
[0]] = parts
[1];
298 entries
[parts
[0]] = String
.Empty
;
305 private object Evaluate(SimpleNode inlineNode
, IInternalContextAdapter context
)
307 ArrayList values
= new ArrayList();
309 for(int i
=0; i
< inlineNode
.ChildrenCount
; i
++)
311 INode node
= inlineNode
.GetChild(i
);
313 if (node
.Type
== ParserTreeConstants
.TEXT
)
315 values
.Add(((ASTText
)node
).Text
);
319 values
.Add(node
.Value(context
));
323 if (values
.Count
== 0)
327 else if (values
.Count
== 1)
333 StringBuilder sb
= new StringBuilder();
334 foreach(object value in values
)
338 return sb
.ToString();
343 /// Processes the first param.
344 /// first param can either be the literal string 'with' which means the user
345 /// is using the syntax #blockcomponent(ComponentName with "param1=value1" "param2=value2")
346 /// or it could be a dictionary string like:
347 /// #blockcomponent(ComponentName "#{ param1='value1', param2='value2' }")
348 /// anything different than that will throw an exception
350 /// <param name="node">The node.</param>
351 /// <param name="context">The context.</param>
352 /// <param name="childrenCount">The children count.</param>
353 /// <returns></returns>
354 private IDictionary
ProcessFirstParam(INode node
, IInternalContextAdapter context
, int childrenCount
)
356 INode withNode
= node
.GetChild(1);
358 String withName
= withNode
.FirstToken
.Image
;
360 if (!"with".Equals(withName
))
362 IDictionary dict
= withNode
.Value(context
) as IDictionary
;
366 if (childrenCount
> 2)
368 String message
= String
.Format("A #{0} directive with a dictionary " +
369 "string parameter cannot have extra params - component {0}", componentName
);
370 throw new ViewComponentException(message
);
376 String message
= String
.Format("A #{0} directive with parameters must use " +
377 "the keyword 'with' - component {0}", componentName
);
378 throw new ViewComponentException(message
);
385 private bool RenderView(IInternalContextAdapter context
,
386 String viewToRender
, Template template
, TextWriter writer
)
390 context
.PushCurrentTemplateName(viewToRender
);
391 ((SimpleNode
) template
.Data
).Render(context
, writer
);
395 if (e
is MethodInvocationException
)
404 context
.PopCurrentTemplateName();
410 private Template
GetTemplate(String viewToRender
, String encoding
)
412 return rsvc
.GetTemplate(viewToRender
, encoding
);
415 private String
ExtractEncoding(IInternalContextAdapter context
)
417 Resource current
= context
.CurrentResource
;
419 String encoding
= null;
423 encoding
= current
.Encoding
;
427 encoding
= (String
) rsvc
.GetProperty(RuntimeConstants
.INPUT_ENCODING
);
433 private void CheckTemplateStack(IInternalContextAdapter context
)
435 Object
[] templateStack
= context
.TemplateNameStack
;
437 if (templateStack
.Length
>= rsvc
.GetInt(RuntimeConstants
.PARSE_DIRECTIVE_MAXDEPTH
, 20))
439 StringBuilder path
= new StringBuilder();
441 for(int i
= 0; i
< templateStack
.Length
; ++i
)
443 path
.Append(" > " + templateStack
[i
]);
446 throw new Exception("Max recursion depth reached (" + templateStack
.Length
+ ")" + " File stack:" + path
);
451 public class CacheAwareContext
: IInternalContextAdapter
453 private readonly IInternalContextAdapter context
;
454 private readonly ViewComponentCacheBag bag
;
456 public CacheAwareContext(IInternalContextAdapter context
, ViewComponentCacheBag bag
)
458 this.context
= context
;
462 object IInternalContextAdapter
.Remove(object key
)
464 if (bag
.ContextEntries
.ContainsKey(key
.ToString()))
466 bag
.ContextEntries
.Remove(key
.ToString());
468 return context
.Remove(key
);
471 public void PushCurrentTemplateName(string s
)
473 context
.PushCurrentTemplateName(s
);
476 public void PopCurrentTemplateName()
478 context
.PopCurrentTemplateName();
481 public IntrospectionCacheData
ICacheGet(object key
)
483 return context
.ICacheGet(key
);
486 public void ICachePut(object key
, IntrospectionCacheData o
)
488 context
.ICachePut(key
, o
);
491 public string CurrentTemplateName
493 get { return context.CurrentTemplateName; }
496 public object[] TemplateNameStack
498 get { return context.TemplateNameStack; }
501 public Resource CurrentResource
503 get { return context.CurrentResource; }
504 set { context.CurrentResource = value; }
507 public object Put(string key
, object value)
509 bag
.ContextEntries
[key
] = value;
511 return context
.Put(key
, value);
514 public object Get(string key
)
516 return context
.Get(key
);
519 public bool ContainsKey(object key
)
521 return context
.ContainsKey(key
);
524 object IContext
.Remove(object key
)
526 if (bag
.ContextEntries
.ContainsKey(key
.ToString()))
528 bag
.ContextEntries
.Remove(key
.ToString());
531 return context
.Remove(key
);
534 public IContext InternalUserContext
536 get { return context.InternalUserContext; }
539 public IInternalContextAdapter BaseContext
541 get { return context.BaseContext; }
544 public EventCartridge
AttachEventCartridge(EventCartridge ec
)
546 return context
.AttachEventCartridge(ec
);
549 public EventCartridge EventCartridge
551 get { return context.EventCartridge; }
554 public bool Contains(object key
)
556 return context
.Contains(key
);
559 public void Add(object key
, object value)
561 bag
.ContextEntries
[key
.ToString()] = value;
562 context
.Add(key
, value);
570 IDictionaryEnumerator IDictionary
.GetEnumerator()
572 return context
.GetEnumerator();
575 public void Remove(object key
)
577 if (bag
.ContextEntries
.ContainsKey(key
.ToString()))
579 bag
.ContextEntries
.Remove(key
.ToString());
582 ((IDictionary
) context
).Remove(key
);
585 public object this[object key
]
587 get { return context[key]; }
590 bag
.ContextEntries
[key
.ToString()] = value;
591 context
[key
] = value;
595 public ICollection Keys
597 get { return ((IDictionary) context).Keys; }
600 object[] IContext
.Keys
602 get { return ((IContext)context).Keys; }
605 public ICollection Values
607 get { return context.Values; }
610 public bool IsReadOnly
612 get { return context.IsReadOnly; }
615 public bool IsFixedSize
617 get { return context.IsFixedSize; }
620 public void CopyTo(Array array
, int index
)
622 context
.CopyTo(array
, index
);
627 get { return ((ICollection) context).Count; }
630 public object SyncRoot
632 get { return context.SyncRoot; }
635 public bool IsSynchronized
637 get { return context.IsSynchronized; }
640 public IEnumerator
GetEnumerator()
642 return ((IEnumerable
) context
).GetEnumerator();