Fixed IOC-96 "FactorySupport fails to create components if the factory instance is...
[castle.git] / MonoRail / Castle.MonoRail.Framework.Views.NVelocity / NVelocityViewEngine.cs
blobba7d453caab2b2a6ca29cb55478987fd46326012
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 /// Evaluates whether the specified template exists.
127 /// </summary>
128 /// <param name="templateName"></param>
129 /// <returns><c>true</c> if it exists</returns>
130 public override bool HasTemplate(String templateName)
132 return ViewSourceLoader.HasTemplate(ResolveTemplateName(templateName));
135 /// <summary>
136 /// Processes the specified context.
137 /// </summary>
138 /// <param name="context">The context.</param>
139 /// <param name="controller">The controller.</param>
140 /// <param name="viewName">Name of the view.</param>
141 public override void Process(IRailsEngineContext context, Controller controller, String viewName)
143 IContext ctx = CreateContext(context, controller);
147 AdjustContentType(context);
149 bool hasLayout = controller.LayoutName != null;
151 TextWriter writer;
153 if (hasLayout)
155 // Because we are rendering within a layout we need to catch it first
156 writer = new StringWriter();
158 else
160 // No layout so render direct to the output
161 writer = context.Response.Output;
164 String view = ResolveTemplateName(viewName);
166 Template template = velocity.GetTemplate(view);
168 PreSendView(controller, template);
170 BeforeMerge(velocity, template, ctx);
171 template.Merge(ctx, writer);
173 PostSendView(controller, template);
175 if (hasLayout)
177 String contents = (writer as StringWriter).GetStringBuilder().ToString();
178 ProcessLayout(contents, controller, ctx, context);
181 catch(Exception ex)
183 if (Logger.IsErrorEnabled)
185 Logger.Error("Could not render view", ex);
188 throw;
192 /// <summary>
193 /// Processes the view - using the templateName to obtain the correct template
194 /// and writes the results to the System.TextWriter. No layout is applied!
195 /// </summary>
196 public override void Process(TextWriter output, IRailsEngineContext context, Controller controller, String viewName)
198 IContext ctx = CreateContext(context, controller);
200 AdjustContentType(context);
202 String view = ResolveTemplateName(viewName);
206 Template template = velocity.GetTemplate(view);
208 BeforeMerge(velocity, template, ctx);
209 template.Merge(ctx, output);
211 catch(Exception ex)
213 if (Logger.IsErrorEnabled)
215 Logger.Error("Could not render view", ex);
218 throw new RailsException("Could not render view: " + view, ex);
222 public override void ProcessPartial(TextWriter output, IRailsEngineContext context,
223 Controller controller, string partialName)
225 IContext ctx = CreateContext(context, controller);
226 String view = ResolveTemplateName(partialName);
230 Template template = velocity.GetTemplate(view);
231 template.Merge(ctx, output);
233 catch(Exception ex)
235 if (Logger.IsErrorEnabled)
237 Logger.Error("Could not render partial", ex);
240 throw new RailsException("Could not render partial " + view, ex);
244 public override object CreateJSGenerator(IRailsEngineContext context)
246 return new JSGeneratorDuck(new PrototypeHelper.JSGenerator(context));
249 public override void GenerateJS(TextWriter output, IRailsEngineContext context, Controller controller,
250 string templateName)
252 IContext ctx = CreateContext(context, controller);
254 object generator = CreateJSGenerator(context);
256 ctx.Put("page", generator);
258 AdjustJavascriptContentType(context);
260 String view = ResolveJSTemplateName(templateName);
264 Template template = velocity.GetTemplate(view);
266 StringWriter writer = new StringWriter();
268 template.Merge(ctx, writer);
270 output.WriteLine(generator);
272 catch(Exception ex)
274 if (Logger.IsErrorEnabled)
276 Logger.Error("Could not generate JS", ex);
279 throw new RailsException("Error generating JS. Template " + templateName, ex);
283 public override void ProcessContents(IRailsEngineContext context, Controller controller, String contents)
285 IContext ctx = CreateContext(context, controller);
286 AdjustContentType(context);
288 bool hasLayout = controller.LayoutName != null;
290 if (hasLayout)
292 ProcessLayout(contents, controller, ctx, context);
294 else
296 context.Response.Output.Write(contents);
300 #endregion
302 /// <summary>
303 /// Initializes basic velocity properties. The main purpose of this method is to
304 /// allow this logic to be overrided.
305 /// </summary>
306 /// <param name="props">The <see cref="ExtendedProperties"/> collection to populate.</param>
307 protected virtual void InitializeVelocityProperties(ExtendedProperties props)
309 velocity.SetApplicationAttribute(RuntimeConstants.RESOURCE_MANAGER_CLASS,
310 new CustomResourceManager(ViewSourceLoader));
312 LoadMacros(props);
315 /// <summary>
316 /// Resolves the template name into a velocity template file name.
317 /// </summary>
318 protected string ResolveTemplateName(string templateName)
320 if (Path.HasExtension(templateName))
322 return templateName;
324 else
326 return templateName + TemplateExtension;
330 /// <summary>
331 /// Resolves the template name into a velocity JS template file name.
332 /// </summary>
333 protected string ResolveJSTemplateName(string templateName)
335 if (Path.HasExtension(templateName))
337 return templateName;
339 else
341 return templateName + JsTemplateExtension;
345 /// <summary>
346 /// Resolves the layout template name into a velocity template file name.
347 /// </summary>
348 protected string ResolveLayoutTemplateName(string templateName)
350 if (templateName.StartsWith("/"))
352 // User is using a folder diferent than the layouts folder
354 return ResolveTemplateName(templateName);
356 else
358 return String.Format("{0}{1}{2}",
359 TemplateKeys.LayoutPath, Path.DirectorySeparatorChar,
360 ResolveTemplateName(templateName));
364 protected virtual void BeforeMerge(VelocityEngine velocityEngine, Template template, IContext context)
368 private void ProcessLayout(String contents, Controller controller, IContext ctx, IRailsEngineContext context)
370 String layout = ResolveLayoutTemplateName(controller.LayoutName);
372 BeforeApplyingLayout(layout, ref contents, controller, ctx, context);
374 RenderLayout(layout, contents, ctx, context, context.Response.Output);
377 protected virtual void BeforeApplyingLayout(string layout, ref string contents,
378 Controller controller, IContext ctx, IRailsEngineContext context)
382 protected void RenderLayout(string layoutName, string contents, IContext ctx, IRailsEngineContext context, TextWriter output)
386 ctx.Put(TemplateKeys.ChildContent, contents);
388 Template template = velocity.GetTemplate(layoutName);
390 BeforeMerge(velocity, template, ctx);
392 template.Merge(ctx, output);
394 catch(Exception ex)
396 if (context.Request.IsLocal)
398 SendErrorDetails(ex, context.Response.Output);
400 else
402 throw new RailsException("Error processing layout. Maybe the layout file does not exists? File: " + layoutName, ex);
407 private IContext CreateContext(IRailsEngineContext context, Controller controller)
409 Hashtable innerContext = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
411 innerContext.Add(TemplateKeys.Controller, controller);
412 innerContext.Add(TemplateKeys.Context, context);
413 innerContext.Add(TemplateKeys.Request, context.Request);
414 innerContext.Add(TemplateKeys.Response, context.Response);
415 innerContext.Add(TemplateKeys.Session, context.Session);
417 if (controller.Resources != null)
419 foreach(String key in controller.Resources.Keys)
421 innerContext[key] = controller.Resources[key];
425 foreach(String key in context.Params.AllKeys)
427 if (key == null) continue; // Nasty bug?
428 object value = context.Params[key];
429 if (value == null) continue;
430 innerContext[key] = value;
433 // list from : http://msdn2.microsoft.com/en-us/library/hfa3fa08.aspx
434 object[] builtInHelpers =
435 new object[]
437 new StaticAccessorHelper<Byte>(),
438 new StaticAccessorHelper<SByte>(),
439 new StaticAccessorHelper<Int16>(),
440 new StaticAccessorHelper<Int32>(),
441 new StaticAccessorHelper<Int64>(),
442 new StaticAccessorHelper<UInt16>(),
443 new StaticAccessorHelper<UInt32>(),
444 new StaticAccessorHelper<UInt64>(),
445 new StaticAccessorHelper<Single>(),
446 new StaticAccessorHelper<Double>(),
447 new StaticAccessorHelper<Boolean>(),
448 new StaticAccessorHelper<Char>(),
449 new StaticAccessorHelper<Decimal>(),
450 new StaticAccessorHelper<String>(),
451 new StaticAccessorHelper<Guid>(),
452 new StaticAccessorHelper<DateTime>()
455 foreach(object helper in builtInHelpers)
457 innerContext[helper.GetType().GetGenericArguments()[0].Name] = helper;
460 if (controller.Helpers != null)
462 foreach (object key in controller.Helpers.Keys)
464 innerContext[key] = controller.Helpers[key];
468 // Adding flash as a collection and each individual item
470 if (context.Flash != null)
472 innerContext[Flash.FlashKey] = context.Flash;
474 foreach(DictionaryEntry entry in context.Flash)
476 if (entry.Value == null) continue;
477 innerContext[entry.Key] = entry.Value;
481 if (controller.PropertyBag != null)
483 foreach(DictionaryEntry entry in controller.PropertyBag)
485 if (entry.Value == null) continue;
486 innerContext[entry.Key] = entry.Value;
490 innerContext[TemplateKeys.SiteRoot] = context.ApplicationPath;
492 return new VelocityContext(innerContext);
495 private void SendErrorDetails(Exception ex, TextWriter writer)
497 writer.WriteLine("<pre>");
498 writer.WriteLine(ex);
499 writer.WriteLine("</pre>");
502 private void LoadMacros(ExtendedProperties props)
504 String[] macros = ViewSourceLoader.ListViews("macros");
506 ArrayList macroList = new ArrayList(macros);
508 if (macroList.Count > 0)
510 object libPropValue = props.GetProperty(RuntimeConstants.VM_LIBRARY);
512 if (libPropValue is ICollection)
514 macroList.AddRange((ICollection) libPropValue);
516 else if (libPropValue is string)
518 macroList.Add(libPropValue);
521 props.AddProperty(RuntimeConstants.VM_LIBRARY, macroList);
524 props.AddProperty(RuntimeConstants.VM_LIBRARY_AUTORELOAD, true);