Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework.Views.NVelocity / NVelocityViewEngine.cs
blob0c72a5fbe6a79c9bcd39d3404d4609813247dba1
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;
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 System.Collections.Generic;
26 using System.Text;
27 using Castle.Core;
28 using Commons.Collections;
29 using JSGeneration;
31 /// <summary>
32 /// Implements a view engine using the popular Velocity syntax.
33 /// <para>
34 /// For details on the syntax, check the VTL Reference Guide
35 /// http://jakarta.apache.org/velocity/docs/vtl-reference-guide.html
36 /// </para>
37 /// </summary>
38 public class NVelocityViewEngine : ViewEngineBase, IInitializable
40 internal const String TemplateExtension = ".vm";
41 internal const String JsTemplateExtension = ".njs";
42 internal const String ServiceProvider = "service.provider";
44 protected readonly VelocityEngine velocity = new VelocityEngine();
46 private IServiceProvider provider;
48 #region IInitializable implementation
50 public void Initialize()
52 ExtendedProperties props = new ExtendedProperties();
54 if (ViewSourceLoader.HasSource("nvelocity.properties"))
56 using(Stream stream = ViewSourceLoader.GetViewSource("nvelocity.properties").OpenViewStream())
58 props.Load(stream);
62 // Set up a custom directive manager
63 props.SetProperty("directive.manager",
64 "Castle.MonoRail.Framework.Views.NVelocity.CustomDirectiveManager; Castle.MonoRail.Framework.Views.NVelocity");
66 InitializeVelocityProperties(props);
68 velocity.SetApplicationAttribute("service.provider", provider);
70 velocity.Init(props);
73 #endregion
75 #region IServiceEnabledComponent implementation
77 public override void Service(IServiceProvider provider)
79 base.Service(provider);
80 this.provider = provider;
83 #endregion
85 #region IViewEngine implementation
87 /// <summary>
88 /// Gets a value indicating whether the view engine
89 /// support the generation of JS.
90 /// </summary>
91 /// <value>
92 /// <c>true</c> if JS generation is supported; otherwise, <c>false</c>.
93 /// </value>
94 public override bool SupportsJSGeneration
96 get { return true; }
99 /// <summary>
100 /// Gets the view file extension.
101 /// </summary>
102 /// <value>The view file extension.</value>
103 public override string ViewFileExtension
105 get { return ".vm"; }
108 /// <summary>
109 /// Gets the JS generator file extension.
110 /// </summary>
111 /// <value>The JS generator file extension.</value>
112 public override string JSGeneratorFileExtension
114 get { return ".njs"; }
117 /// <summary>
118 /// Pendent.
119 /// </summary>
120 /// <param name="output">The output.</param>
121 /// <param name="context">The context.</param>
122 /// <param name="controller">The controller.</param>
123 /// <param name="controllerContext">The controller context.</param>
124 /// <param name="viewName">Name of the view.</param>
125 public override void Process(String viewName, TextWriter output, IEngineContext context,
126 IController controller, IControllerContext controllerContext)
128 IContext ctx = CreateContext(context, controller, controllerContext);
132 AdjustContentType(context);
134 bool hasLayout = controllerContext.LayoutNames != null && controllerContext.LayoutNames.Length != 0;
136 TextWriter writer;
138 if (hasLayout)
140 // Because we are rendering within a layout we need to catch it first
141 writer = new StringWriter();
143 else
145 // No layout so render direct to the output
146 writer = output;
149 String view = ResolveTemplateName(viewName);
151 Template template = velocity.GetTemplate(view);
153 PreSendView(controller, template);
155 BeforeMerge(velocity, template, ctx);
156 template.Merge(ctx, writer);
158 PostSendView(controller, template);
160 if (hasLayout)
162 ProcessLayoutRecursively((StringWriter) writer, context, controller, controllerContext, ctx, output);
165 catch(Exception ex)
167 if (Logger.IsErrorEnabled)
169 Logger.Error("Could not render view", ex);
172 throw;
176 public override void Process(string templateName, string layoutName,
177 TextWriter output, IDictionary<string, object> parameters)
179 IContext ctx = CreateContext(parameters);
183 bool hasLayout = layoutName != null;
185 TextWriter writer;
187 if (hasLayout)
189 // Because we are rendering within a layout we need to catch it first
190 writer = new StringWriter();
192 else
194 // No layout so render direct to the output
195 writer = output;
198 String view = ResolveTemplateName(templateName);
200 Template template = velocity.GetTemplate(view);
202 BeforeMerge(velocity, template, ctx);
203 template.Merge(ctx, writer);
205 if (hasLayout)
207 String contents = (writer as StringWriter).GetStringBuilder().ToString();
208 ProcessLayout(contents, layoutName, ctx, output);
211 catch(Exception ex)
213 if (Logger.IsErrorEnabled)
215 Logger.Error("Could not render view", ex);
218 throw;
222 public override void ProcessPartial(String partialName, TextWriter output, IEngineContext context,
223 IController controller, IControllerContext controllerContext)
225 IContext ctx = CreateContext(context, controller, controllerContext);
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 MonoRailException("Could not render partial " + view, ex);
244 public override object CreateJSGenerator(JSCodeGeneratorInfo generatorInfo,
245 IEngineContext context, IController controller,
246 IControllerContext controllerContext)
248 return new JSGeneratorDuck(generatorInfo.CodeGenerator, generatorInfo.LibraryGenerator,
249 generatorInfo.Extensions, generatorInfo.ElementExtensions);
252 public override void GenerateJS(String templateName, TextWriter output, JSCodeGeneratorInfo generatorInfo,
253 IEngineContext context, IController controller,
254 IControllerContext controllerContext)
256 IContext ctx = CreateContext(context, controller, controllerContext);
258 object generator = CreateJSGenerator(generatorInfo, context, controller, controllerContext);
260 ctx.Put("page", generator);
262 AdjustJavascriptContentType(context);
264 String view = ResolveJSTemplateName(templateName);
268 Template template = velocity.GetTemplate(view);
270 StringWriter writer = new StringWriter();
272 template.Merge(ctx, writer);
274 output.WriteLine(generator);
276 catch(Exception ex)
278 if (Logger.IsErrorEnabled)
280 Logger.Error("Could not generate JS", ex);
283 throw new MonoRailException("Error generating JS. Template " + templateName, ex);
287 public override void RenderStaticWithinLayout(String contents, IEngineContext context, IController controller,
288 IControllerContext controllerContext)
290 IContext ctx = CreateContext(context, controller, controllerContext);
291 AdjustContentType(context);
293 bool hasLayout = controllerContext.LayoutNames != null && controllerContext.LayoutNames.Length != 0;
295 if (hasLayout)
297 ProcessLayout(contents, controller, controllerContext, ctx, context, context.Response.Output);
299 else
301 context.Response.Output.Write(contents);
305 #endregion
307 /// <summary>
308 /// Initializes basic velocity properties. The main purpose of this method is to
309 /// allow this logic to be overrided.
310 /// </summary>
311 /// <param name="props">The <see cref="ExtendedProperties"/> collection to populate.</param>
312 protected virtual void InitializeVelocityProperties(ExtendedProperties props)
314 velocity.SetApplicationAttribute(RuntimeConstants.RESOURCE_MANAGER_CLASS,
315 new CustomResourceManager(ViewSourceLoader));
317 LoadMacros(props);
320 /// <summary>
321 /// Resolves the layout template name into a velocity template file name.
322 /// </summary>
323 protected string ResolveLayoutTemplateName(string templateName)
325 if (templateName.StartsWith("/"))
327 // User is using a folder diferent than the layouts folder
329 return ResolveTemplateName(templateName);
331 else
333 return String.Format("{0}{1}{2}",
334 TemplateKeys.LayoutPath, Path.DirectorySeparatorChar,
335 ResolveTemplateName(templateName));
339 protected virtual void BeforeMerge(VelocityEngine velocityEngine, Template template, IContext context)
343 private void ProcessLayoutRecursively(StringWriter writer, IEngineContext context,
344 IController controller, IControllerContext controllerContext,
345 IContext ctx, TextWriter finalOutput)
347 for(int i = controllerContext.LayoutNames.Length - 1; i >= 0; i--)
349 string layoutName = ResolveLayoutTemplateName(controllerContext.LayoutNames[i]);
351 string contents = writer.GetStringBuilder().ToString();
353 BeforeApplyingLayout(layoutName, ref contents, controller, controllerContext, ctx, context);
355 writer.GetStringBuilder().Length = 0;
357 RenderLayout(layoutName, contents, ctx, i == 0 ? finalOutput : writer);
361 private void ProcessLayout(String contents, string layoutName, IContext ctx, TextWriter output)
363 RenderLayout(layoutName, contents, ctx, output);
366 private void ProcessLayout(String contents, IController controller, IControllerContext controllerContext, IContext ctx,
367 IEngineContext context, TextWriter output)
369 String layout = ResolveLayoutTemplateName(controllerContext.LayoutNames[0]);
371 BeforeApplyingLayout(layout, ref contents, controller, controllerContext, ctx, context);
373 RenderLayout(layout, contents, ctx, output);
376 protected virtual void BeforeApplyingLayout(string layout, ref string contents,
377 IController controller, IControllerContext controllerContext,
378 IContext ctx, IEngineContext context)
382 protected void RenderLayout(string layoutName, string contents, IContext ctx, TextWriter output)
384 ctx.Put(TemplateKeys.ChildContent, contents);
386 Template template = velocity.GetTemplate(layoutName);
388 BeforeMerge(velocity, template, ctx);
390 template.Merge(ctx, output);
393 private IContext CreateContext(IDictionary<string, object> parameters)
395 Hashtable innerContext = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
397 foreach(KeyValuePair<string, object> pair in parameters)
399 innerContext[pair.Key] = pair.Value;
402 return new VelocityContext(innerContext);
405 private IContext CreateContext(IEngineContext context, IController controller, IControllerContext controllerContext)
407 IRequest request = context.Request;
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 (controllerContext.Resources != null)
419 foreach(String key in controllerContext.Resources.Keys)
421 innerContext[key] = controllerContext.Resources[key];
425 foreach(String key in request.QueryString.AllKeys)
427 if (key == null) continue;
428 object value = request.QueryString[key];
429 if (value == null) continue;
430 innerContext[key] = value;
432 foreach(String key in request.Form.AllKeys)
434 if (key == null) continue;
435 object value = request.Form[key];
436 if (value == null) continue;
437 innerContext[key] = value;
440 // list from : http://msdn2.microsoft.com/en-us/library/hfa3fa08.aspx
441 object[] builtInHelpers =
442 new object[]
444 new StaticAccessorHelper<Byte>(),
445 new StaticAccessorHelper<SByte>(),
446 new StaticAccessorHelper<Int16>(),
447 new StaticAccessorHelper<Int32>(),
448 new StaticAccessorHelper<Int64>(),
449 new StaticAccessorHelper<UInt16>(),
450 new StaticAccessorHelper<UInt32>(),
451 new StaticAccessorHelper<UInt64>(),
452 new StaticAccessorHelper<Single>(),
453 new StaticAccessorHelper<Double>(),
454 new StaticAccessorHelper<Boolean>(),
455 new StaticAccessorHelper<Char>(),
456 new StaticAccessorHelper<Decimal>(),
457 new StaticAccessorHelper<String>(),
458 new StaticAccessorHelper<Guid>(),
459 new StaticAccessorHelper<DateTime>()
462 foreach(object helper in builtInHelpers)
464 innerContext[helper.GetType().GetGenericArguments()[0].Name] = helper;
467 if (controllerContext.Helpers != null)
469 foreach(object key in controllerContext.Helpers.Keys)
471 innerContext[key] = controllerContext.Helpers[key];
475 // Adding flash as a collection and each individual item
477 if (context.Flash != null)
479 innerContext[Flash.FlashKey] = context.Flash;
481 foreach(DictionaryEntry entry in context.Flash)
483 if (entry.Value == null) continue;
484 innerContext[entry.Key] = entry.Value;
488 if (controllerContext.PropertyBag != null)
490 foreach(DictionaryEntry entry in controllerContext.PropertyBag)
492 if (entry.Value == null) continue;
493 innerContext[entry.Key] = entry.Value;
497 innerContext[TemplateKeys.SiteRoot] = context.ApplicationPath;
499 return new VelocityContext(innerContext);
502 private void LoadMacros(ExtendedProperties props)
504 String[] macros = ViewSourceLoader.ListViews("macros",this.ViewFileExtension,this.JSGeneratorFileExtension);
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);