- If the controller cannot be found, MR searches for a special rescue "rescues/404...
[castle.git] / MonoRail / Castle.MonoRail.Framework.Views.NVelocity / CustomDirectives / AbstractComponentDirective.cs
blob7813b0a4c3a68d03cd874302a657eda4824f239d
1 // Copyright 2004-2007 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 IInternalContextAdapter = NVelocity.Context.IInternalContextAdapter;
22 using Template = NVelocity.Template;
24 namespace Castle.MonoRail.Framework.Views.NVelocity.CustomDirectives
26 using System;
27 using System.Collections.Specialized;
28 using System.IO;
29 using System.Text;
30 using System.Collections;
32 using Castle.MonoRail.Framework;
34 /// <summary>
35 /// Pendent
36 /// </summary>
37 public abstract class AbstractComponentDirective : Directive, IViewRenderer
39 private readonly IViewComponentFactory viewComponentFactory;
41 private String componentName;
42 private ViewComponent component;
43 private NVelocityViewContextAdapter contextAdapter;
44 private IViewEngine viewEngine;
45 private INode compNameNode;
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 void Init(IRuntimeServices rs, IInternalContextAdapter context, INode node)
60 base.Init(rs, context, node);
62 compNameNode = node.GetChild(0);
64 if (compNameNode == null)
66 String message = String.Format("You must specify the component name on the #{0} directive", Name);
67 throw new ViewComponentException(message);
71 public override bool Render(IInternalContextAdapter context, TextWriter writer, INode node)
73 componentName = compNameNode.FirstToken.Image;
75 if (componentName == null)
77 String message = String.Format("Could not obtain component name from the #{0} directive", Name);
78 throw new ViewComponentException(message);
81 if (componentName.StartsWith("$"))
83 String nodeContent = compNameNode.Literal.Trim('"', '\'');
84 SimpleNode inlineNode = rsvc.Parse(new StringReader(nodeContent), context.CurrentTemplateName, false);
86 inlineNode.Init(context, rsvc);
87 componentName = (String) Evaluate(inlineNode, context);
90 component = viewComponentFactory.Create(componentName);
92 ASTDirective directiveNode = (ASTDirective) node;
93 IViewRenderer renderer = (IViewRenderer) directiveNode.Directive;
95 contextAdapter = new NVelocityViewContextAdapter(componentName, node, viewEngine, renderer);
96 contextAdapter.Context = context;
98 ProcessSubSections();
100 INode bodyNode = null;
102 IDictionary componentParams = CreateParameters(context, node);
104 if (node.ChildrenCount > 0)
106 bodyNode = node.GetChild(node.ChildrenCount - 1);
109 contextAdapter.BodyNode = bodyNode;
110 contextAdapter.ComponentParams = componentParams;
111 contextAdapter.TextWriter = writer;
113 IRailsEngineContext railsContext = MonoRailHttpHandler.CurrentContext;
115 const string ViewComponentContextKey = "viewcomponent";
119 contextAdapter.ContextVars[ViewComponentContextKey] = component;
121 component.Init(railsContext, contextAdapter);
123 component.Render();
125 if (contextAdapter.ViewToRender != null)
127 return RenderComponentView(context, contextAdapter.ViewToRender, writer, contextAdapter);
130 finally
132 contextAdapter.ContextVars.Remove(ViewComponentContextKey);
135 return true;
138 public bool RenderComponentView(IInternalContextAdapter context, String viewToRender, TextWriter writer, NVelocityViewContextAdapter contextAdapter)
140 foreach(DictionaryEntry entry in contextAdapter.ContextVars)
142 context.Put(entry.Key.ToString(), entry.Value);
145 viewToRender = viewToRender + NVelocityViewEngine.TemplateExtension;
147 CheckTemplateStack(context);
149 String encoding = SetUpEncoding(context);
151 Template template = GetTemplate(viewToRender, encoding);
153 return RenderView(context, viewToRender, template, writer);
156 protected virtual void ProcessSubSections()
160 protected virtual IDictionary CreateParameters(IInternalContextAdapter context, INode node)
162 int childrenCount = node.ChildrenCount;
164 if (childrenCount > 1)
166 INode lastNode = node.GetChild(childrenCount - 1);
168 if (lastNode.Type == ParserTreeConstants.BLOCK)
170 childrenCount--;
173 if (childrenCount > 1)
175 IDictionary dict = ProcessFirstParam(node, context, childrenCount);
177 if (dict != null)
179 return dict;
181 else if (childrenCount > 2)
183 return ProcessRemainingParams(childrenCount, node, context);
188 return new Hashtable(0);
191 protected string ComponentName
193 get { return componentName; }
196 protected ViewComponent Component
198 get { return component; }
201 protected NVelocityViewContextAdapter ContextAdapter
203 get { return contextAdapter; }
206 private IDictionary ProcessRemainingParams(int childrenCount, INode node, IInternalContextAdapter context)
208 IDictionary entries = new HybridDictionary(true);
210 for(int i = 2; i < childrenCount; i++)
212 INode paramNode = node.GetChild(i);
214 string nodeContent = paramNode.Literal.TrimStart('"', '\'').TrimEnd('"', '\'');
216 string[] parts = nodeContent.Split(new char[] {'='}, 2, StringSplitOptions.RemoveEmptyEntries);
218 if (parts.Length == 2 && parts[1].IndexOf("$") != -1)
220 SimpleNode inlineNode = rsvc.Parse(new StringReader(parts[1]), context.CurrentTemplateName, false);
222 inlineNode.Init(context, rsvc);
224 entries[parts[0]] = Evaluate(inlineNode, context);
226 else if (parts.Length == 2)
228 entries[parts[0]] = parts[1];
230 else
232 entries[parts[0]] = String.Empty;
236 return entries;
239 private object Evaluate(SimpleNode inlineNode, IInternalContextAdapter context)
241 ArrayList values = new ArrayList();
243 for(int i=0; i < inlineNode.ChildrenCount; i++)
245 INode node = inlineNode.GetChild(i);
247 if (node.Type == ParserTreeConstants.TEXT)
249 values.Add(((ASTText)node).Text);
251 else
253 values.Add(node.Value(context));
257 if (values.Count == 0)
259 return null;
261 else if (values.Count == 1)
263 return values[0];
265 else
267 StringBuilder sb = new StringBuilder();
268 foreach(object value in values)
270 sb.Append(value);
272 return sb.ToString();
276 /// <summary>
277 /// Processes the first param.
278 /// first param can either be the literal string 'with' which means the user
279 /// is using the syntax #blockcomponent(ComponentName with "param1=value1" "param2=value2")
280 /// or it could be a dictionary string like:
281 /// #blockcomponent(ComponentName "#{ param1='value1', param2='value2' }")
282 /// anything different than that will throw an exception
283 /// </summary>
284 /// <param name="node">The node.</param>
285 /// <param name="context">The context.</param>
286 /// <param name="childrenCount">The children count.</param>
287 /// <returns></returns>
288 private IDictionary ProcessFirstParam(INode node, IInternalContextAdapter context, int childrenCount)
290 INode withNode = node.GetChild(1);
292 String withName = withNode.FirstToken.Image;
294 if (!"with".Equals(withName))
296 IDictionary dict = withNode.Value(context) as IDictionary;
298 if (dict != null)
300 if (childrenCount > 2)
302 String message = String.Format("A #{0} directive with a dictionary " +
303 "string param cannot have extra params - component {0}", componentName, Name);
304 throw new ViewComponentException(message);
306 return dict;
308 else
310 String message = String.Format("A #{0} directive with parameters must use " +
311 "the keyword 'with' - component {0}", componentName, Name);
312 throw new ViewComponentException(message);
316 return null;
319 private bool RenderView(IInternalContextAdapter context,
320 String viewToRender, Template template, TextWriter writer)
324 context.PushCurrentTemplateName(viewToRender);
325 ((SimpleNode) template.Data).Render(context, writer);
327 catch(Exception e)
329 if (e is MethodInvocationException)
331 throw;
334 return false;
336 finally
338 context.PopCurrentTemplateName();
341 return true;
344 private Template GetTemplate(String viewToRender, String encoding)
348 return rsvc.GetTemplate(viewToRender, encoding);
350 catch(Exception)
352 throw;
356 private String SetUpEncoding(IInternalContextAdapter context)
358 Resource current = context.CurrentResource;
360 String encoding = null;
362 if (current != null)
364 encoding = current.Encoding;
366 else
368 encoding = (String) rsvc.GetProperty(RuntimeConstants.INPUT_ENCODING);
370 return encoding;
373 private void CheckTemplateStack(IInternalContextAdapter context)
375 Object[] templateStack = context.TemplateNameStack;
377 if (templateStack.Length >= rsvc.GetInt(RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 20))
379 StringBuilder path = new StringBuilder();
381 for(int i = 0; i < templateStack.Length; ++i)
383 path.Append(" > " + templateStack[i]);
386 throw new Exception("Max recursion depth reached (" + templateStack.Length + ")" + " File stack:" + path);