Refactored the Kernel registration fluent interface to be more readable, better suppo...
[castle.git] / MonoRail / Castle.MonoRail.Framework.Views.NVelocity / CustomDirectives / AbstractComponentDirective.cs
blob9c7630070b4286d589d50a1e4b55b18f0146de2b
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 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;
24 using NVelocity;
26 namespace Castle.MonoRail.Framework.Views.NVelocity.CustomDirectives
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.IO;
33 using System.Text;
34 using Castle.MonoRail.Framework;
35 using Castle.MonoRail.Framework.Internal;
36 using Descriptors;
37 using Providers;
39 /// <summary>
40 /// Pendent
41 /// </summary>
42 public abstract class AbstractComponentDirective : Directive, IViewRenderer
44 private readonly IViewComponentFactory viewComponentFactory;
45 private IViewEngine viewEngine;
47 /// <summary>
48 /// Initializes a new instance of the <see cref="AbstractComponentDirective"/> class.
49 /// </summary>
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;
96 CacheKey key = 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);
110 if (key != null)
112 ViewComponentCacheBag cachedContent = (ViewComponentCacheBag) cacheProvider.Get(key.ToString());
114 if (cachedContent != null)
116 // Restore entries
118 foreach(KeyValuePair<string, object> pair in cachedContent.ContextEntries)
120 context[pair.Key] = pair.Value;
123 // Render from cache
125 writer.Write(cachedContent.Content);
127 return true;
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);
172 component.Render();
174 if (contextAdapter.ViewToRender != null)
176 RenderComponentView(context, contextAdapter.ViewToRender, output, contextAdapter);
179 if (isOutputtingToCache)
181 // Save output
183 cacheProvider.Store(key.ToString(), bag);
185 // Output to correct writer
187 writer.Write(bag.Content);
190 finally
192 context.Remove(ViewComponentContextKey);
195 return true;
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)
227 childrenCount--;
230 if (childrenCount > 1)
232 IDictionary dict = ProcessFirstParam(node, context, childrenCount);
234 if (dict != null)
236 return dict;
238 else if (childrenCount > 2)
240 return ProcessRemainingParams(childrenCount, node, context);
245 return new Hashtable(0);
248 // protected string ComponentName
249 // {
250 // get { return componentName; }
251 // }
253 // protected ViewComponent Component
254 // {
255 // get { return component; }
256 // }
258 // protected NVelocityViewContextAdapter ContextAdapter
259 // {
260 // get { return contextAdapter; }
261 // }
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];
287 else
289 entries[parts[0]] = String.Empty;
293 return entries;
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);
308 else
310 values.Add(node.Value(context));
314 if (values.Count == 0)
316 return null;
318 else if (values.Count == 1)
320 return values[0];
322 else
324 StringBuilder sb = new StringBuilder();
325 foreach(object value in values)
327 sb.Append(value);
329 return sb.ToString();
333 /// <summary>
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
340 /// </summary>
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;
358 if (dict != null)
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);
366 return dict;
368 else
370 String message = String.Format("A #{0} directive with parameters must use " +
371 "the keyword 'with' - component {0}", componentName);
372 throw new ViewComponentException(message);
376 return null;
379 private bool RenderView(IInternalContextAdapter context,
380 String viewToRender, Template template, TextWriter writer)
384 context.PushCurrentTemplateName(viewToRender);
385 ((SimpleNode) template.Data).Render(context, writer);
387 finally
389 context.PopCurrentTemplateName();
392 return true;
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;
404 String encoding;
406 if (current != null)
408 encoding = current.Encoding;
410 else
412 encoding = (String) runtimeServices.GetProperty(RuntimeConstants.INPUT_ENCODING);
415 return 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;
444 this.bag = bag;
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);
550 public void Clear()
552 context.Clear();
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);
610 public int Count
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();