Minor style changes
[castle.git] / MonoRail / Castle.MonoRail.Framework.Views.NVelocity / NVelocityViewEngine.cs
blobca868390c018b1605085ef29239d7cfd3b619521
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;
16 using NVelocity.App;
17 using NVelocity.Context;
18 using NVelocity.Runtime;
20 namespace Castle.MonoRail.Framework.Views.NVelocity
22 using System;
23 using System.IO;
24 using System.Collections;
25 using Castle.Core;
26 using Castle.MonoRail.Framework.Helpers;
27 using Castle.MonoRail.Framework.Views.NVelocity.JSGeneration;
28 using Commons.Collections;
30 /// <summary>
31 /// Implements a view engine using the popular Velocity syntax.
32 /// <para>
33 /// For details on the syntax, check the VTL Reference Guide
34 /// http://jakarta.apache.org/velocity/docs/vtl-reference-guide.html
35 /// </para>
36 /// </summary>
37 public class NVelocityViewEngine : ViewEngineBase, IInitializable
39 internal const String TemplateExtension = ".vm";
40 internal const String JsTemplateExtension = ".njs";
42 internal const String ServiceProvider = "service.provider";
44 private IServiceProvider provider;
46 protected VelocityEngine velocity = new VelocityEngine();
48 /// <summary>
49 /// Creates a new <see cref="NVelocityViewEngine"/> instance.
50 /// </summary>
51 public NVelocityViewEngine()
55 #region IInitializable implementation
57 public void Initialize()
59 ExtendedProperties props = new ExtendedProperties();
61 if (ViewSourceLoader.HasTemplate("nvelocity.properties"))
63 using (Stream stream = ViewSourceLoader.GetViewSource("nvelocity.properties").OpenViewStream())
65 props.Load(stream);
69 // Set up a custom directive manager
70 props.SetProperty("directive.manager",
71 "Castle.MonoRail.Framework.Views.NVelocity.CustomDirectiveManager; Castle.MonoRail.Framework.Views.NVelocity");
73 InitializeVelocityProperties(props);
75 velocity.SetApplicationAttribute("service.provider", provider);
77 velocity.Init(props);
80 #endregion
82 #region IServiceEnabledComponent implementation
84 public override void Service(IServiceProvider provider)
86 base.Service(provider);
88 this.provider = provider;
91 #endregion
93 #region IViewEngine implementation
95 /// <summary>
96 /// Gets a value indicating whether the view engine
97 /// support the generation of JS.
98 /// </summary>
99 /// <value>
100 /// <c>true</c> if JS generation is supported; otherwise, <c>false</c>.
101 /// </value>
102 public override bool SupportsJSGeneration
104 get { return true; }
107 /// <summary>
108 /// Gets the view file extension.
109 /// </summary>
110 /// <value>The view file extension.</value>
111 public override string ViewFileExtension
113 get { return ".vm"; }
116 /// <summary>
117 /// Gets the JS generator file extension.
118 /// </summary>
119 /// <value>The JS generator file extension.</value>
120 public override string JSGeneratorFileExtension
122 get { return ".njs"; }
125 /// <summary>
126 /// Processes the specified context.
127 /// </summary>
128 /// <param name="context">The context.</param>
129 /// <param name="controller">The controller.</param>
130 /// <param name="viewName">Name of the view.</param>
131 public override void Process(IRailsEngineContext context, IController controller, String viewName)
133 IContext ctx = CreateContext(context, controller);
137 AdjustContentType(context);
139 bool hasLayout = controller.LayoutName != null;
141 TextWriter writer;
143 if (hasLayout)
145 // Because we are rendering within a layout we need to catch it first
146 writer = new StringWriter();
148 else
150 // No layout so render direct to the output
151 writer = context.Response.Output;
154 String view = ResolveTemplateName(viewName);
156 Template template = velocity.GetTemplate(view);
158 PreSendView(controller, template);
160 BeforeMerge(velocity, template, ctx);
161 template.Merge(ctx, writer);
163 PostSendView(controller, template);
165 if (hasLayout)
167 String contents = (writer as StringWriter).GetStringBuilder().ToString();
168 ProcessLayout(contents, controller, ctx, context);
171 catch(Exception ex)
173 if (Logger.IsErrorEnabled)
175 Logger.Error("Could not render view", ex);
178 throw;
182 /// <summary>
183 /// Processes the view - using the templateName to obtain the correct template
184 /// and writes the results to the System.TextWriter. No layout is applied!
185 /// </summary>
186 public override void Process(TextWriter output, IRailsEngineContext context, IController controller, String viewName)
188 IContext ctx = CreateContext(context, controller);
190 AdjustContentType(context);
192 String view = ResolveTemplateName(viewName);
196 Template template = velocity.GetTemplate(view);
198 BeforeMerge(velocity, template, ctx);
199 template.Merge(ctx, output);
201 catch (Exception ex)
203 if (Logger.IsErrorEnabled)
205 Logger.Error("Could not render view", ex);
208 throw new RailsException("Could not render view: " + view, ex);
212 public override void ProcessPartial(TextWriter output, IRailsEngineContext context,
213 IController controller, string partialName)
215 IContext ctx = CreateContext(context, controller);
216 String view = ResolveTemplateName(partialName);
220 Template template = velocity.GetTemplate(view);
221 template.Merge(ctx, output);
223 catch (Exception ex)
225 if (Logger.IsErrorEnabled)
227 Logger.Error("Could not render partial", ex);
230 throw new RailsException("Could not render partial " + view, ex);
234 public override object CreateJSGenerator(IRailsEngineContext context)
236 return new JSGeneratorDuck(new PrototypeHelper.JSGenerator(context));
239 public override void GenerateJS(TextWriter output, IRailsEngineContext context, IController controller,
240 string templateName)
242 IContext ctx = CreateContext(context, controller);
244 object generator = CreateJSGenerator(context);
246 ctx.Put("page", generator);
248 AdjustJavascriptContentType(context);
250 String view = ResolveJSTemplateName(templateName);
254 Template template = velocity.GetTemplate(view);
256 StringWriter writer = new StringWriter();
258 template.Merge(ctx, writer);
260 output.WriteLine(generator);
262 catch (Exception ex)
264 if (Logger.IsErrorEnabled)
266 Logger.Error("Could not generate JS", ex);
269 throw new RailsException("Error generating JS. Template " + templateName, ex);
273 public override void ProcessContents(IRailsEngineContext context, IController controller, String contents)
275 IContext ctx = CreateContext(context, controller);
276 AdjustContentType(context);
278 bool hasLayout = controller.LayoutName != null;
280 if (hasLayout)
282 ProcessLayout(contents, controller, ctx, context);
284 else
286 context.Response.Output.Write(contents);
290 #endregion
292 /// <summary>
293 /// Initializes basic velocity properties. The main purpose of this method is to
294 /// allow this logic to be overrided.
295 /// </summary>
296 /// <param name="props">The <see cref="ExtendedProperties"/> collection to populate.</param>
297 protected virtual void InitializeVelocityProperties(ExtendedProperties props)
299 velocity.SetApplicationAttribute(RuntimeConstants.RESOURCE_MANAGER_CLASS,
300 new CustomResourceManager(ViewSourceLoader));
302 LoadMacros(props);
305 /// <summary>
306 /// Resolves the layout template name into a velocity template file name.
307 /// </summary>
308 protected string ResolveLayoutTemplateName(string templateName)
310 if (templateName.StartsWith("/"))
312 // User is using a folder diferent than the layouts folder
314 return ResolveTemplateName(templateName);
316 else
318 return String.Format("{0}{1}{2}",
319 TemplateKeys.LayoutPath, Path.DirectorySeparatorChar,
320 ResolveTemplateName(templateName));
324 protected virtual void BeforeMerge(VelocityEngine velocityEngine, Template template, IContext context)
328 private void ProcessLayout(String contents, IController controller, IContext ctx, IRailsEngineContext context)
330 String layout = ResolveLayoutTemplateName(controller.LayoutName);
332 BeforeApplyingLayout(layout, ref contents, controller, ctx, context);
334 RenderLayout(layout, contents, ctx, context, context.Response.Output);
337 protected virtual void BeforeApplyingLayout(string layout, ref string contents,
338 IController controller, IContext ctx, IRailsEngineContext context)
342 protected void RenderLayout(string layoutName, string contents, IContext ctx, IRailsEngineContext context, TextWriter output)
346 ctx.Put(TemplateKeys.ChildContent, contents);
348 Template template = velocity.GetTemplate(layoutName);
350 BeforeMerge(velocity, template, ctx);
352 template.Merge(ctx, output);
354 catch (Exception ex)
356 if (context.Request.IsLocal)
358 SendErrorDetails(ex, context.Response.Output);
360 else
362 throw new RailsException("Error processing layout. Maybe the layout file does not exists? File: " + layoutName, ex);
367 private IContext CreateContext(IRailsEngineContext context, IController controller)
369 Hashtable innerContext = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
371 innerContext.Add(TemplateKeys.Controller, controller);
372 innerContext.Add(TemplateKeys.Context, context);
373 innerContext.Add(TemplateKeys.Request, context.Request);
374 innerContext.Add(TemplateKeys.Response, context.Response);
375 innerContext.Add(TemplateKeys.Session, context.Session);
377 if (controller.Resources != null)
379 foreach (String key in controller.Resources.Keys)
381 innerContext[key] = controller.Resources[key];
385 foreach (String key in context.Params.AllKeys)
387 if (key == null) continue; // Nasty bug?
388 object value = context.Params[key];
389 if (value == null) continue;
390 innerContext[key] = value;
393 // list from : http://msdn2.microsoft.com/en-us/library/hfa3fa08.aspx
394 object[] builtInHelpers =
395 new object[]
397 new StaticAccessorHelper<Byte>(),
398 new StaticAccessorHelper<SByte>(),
399 new StaticAccessorHelper<Int16>(),
400 new StaticAccessorHelper<Int32>(),
401 new StaticAccessorHelper<Int64>(),
402 new StaticAccessorHelper<UInt16>(),
403 new StaticAccessorHelper<UInt32>(),
404 new StaticAccessorHelper<UInt64>(),
405 new StaticAccessorHelper<Single>(),
406 new StaticAccessorHelper<Double>(),
407 new StaticAccessorHelper<Boolean>(),
408 new StaticAccessorHelper<Char>(),
409 new StaticAccessorHelper<Decimal>(),
410 new StaticAccessorHelper<String>(),
411 new StaticAccessorHelper<Guid>(),
412 new StaticAccessorHelper<DateTime>()
415 foreach (object helper in builtInHelpers)
417 innerContext[helper.GetType().GetGenericArguments()[0].Name] = helper;
420 if (controller.Helpers != null)
422 foreach (object key in controller.Helpers.Keys)
424 innerContext[key] = controller.Helpers[key];
428 // Adding flash as a collection and each individual item
430 if (context.Flash != null)
432 innerContext[Flash.FlashKey] = context.Flash;
434 foreach (DictionaryEntry entry in context.Flash)
436 if (entry.Value == null) continue;
437 innerContext[entry.Key] = entry.Value;
441 if (controller.PropertyBag != null)
443 foreach (DictionaryEntry entry in controller.PropertyBag)
445 if (entry.Value == null) continue;
446 innerContext[entry.Key] = entry.Value;
450 innerContext[TemplateKeys.SiteRoot] = context.ApplicationPath;
452 return new VelocityContext(innerContext);
455 private void SendErrorDetails(Exception ex, TextWriter writer)
457 writer.WriteLine("<pre>");
458 writer.WriteLine(ex);
459 writer.WriteLine("</pre>");
462 private void LoadMacros(ExtendedProperties props)
464 String[] macros = ViewSourceLoader.ListViews("macros");
466 ArrayList macroList = new ArrayList(macros);
468 if (macroList.Count > 0)
470 object libPropValue = props.GetProperty(RuntimeConstants.VM_LIBRARY);
472 if (libPropValue is ICollection)
474 macroList.AddRange((ICollection)libPropValue);
476 else if (libPropValue is string)
478 macroList.Add(libPropValue);
481 props.AddProperty(RuntimeConstants.VM_LIBRARY, macroList);
484 props.AddProperty(RuntimeConstants.VM_LIBRARY_AUTORELOAD, true);