1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 using NVelocity
.Context
;
18 using NVelocity
.Runtime
;
20 namespace Castle
.MonoRail
.Framework
.Views
.NVelocity
24 using System
.Collections
;
25 using System
.Collections
.Generic
;
28 using Commons
.Collections
;
32 /// Implements a view engine using the popular Velocity syntax.
34 /// For details on the syntax, check the VTL Reference Guide
35 /// http://jakarta.apache.org/velocity/docs/vtl-reference-guide.html
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())
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
);
75 #region IServiceEnabledComponent implementation
77 public override void Service(IServiceProvider provider
)
79 base.Service(provider
);
80 this.provider
= provider
;
85 #region IViewEngine implementation
88 /// Gets a value indicating whether the view engine
89 /// support the generation of JS.
92 /// <c>true</c> if JS generation is supported; otherwise, <c>false</c>.
94 public override bool SupportsJSGeneration
100 /// Gets the view file extension.
102 /// <value>The view file extension.</value>
103 public override string ViewFileExtension
105 get { return ".vm"; }
109 /// Gets the JS generator file extension.
111 /// <value>The JS generator file extension.</value>
112 public override string JSGeneratorFileExtension
114 get { return ".njs"; }
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;
140 // Because we are rendering within a layout we need to catch it first
141 writer
= new StringWriter();
145 // No layout so render direct to the 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
);
162 ProcessLayoutRecursively((StringWriter
) writer
, context
, controller
, controllerContext
, ctx
, output
);
167 if (Logger
.IsErrorEnabled
)
169 Logger
.Error("Could not render view", ex
);
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;
189 // Because we are rendering within a layout we need to catch it first
190 writer
= new StringWriter();
194 // No layout so render direct to the output
198 String view
= ResolveTemplateName(templateName
);
200 Template template
= velocity
.GetTemplate(view
);
202 BeforeMerge(velocity
, template
, ctx
);
203 template
.Merge(ctx
, writer
);
207 String contents
= (writer
as StringWriter
).GetStringBuilder().ToString();
208 ProcessLayout(contents
, layoutName
, ctx
, output
);
213 if (Logger
.IsErrorEnabled
)
215 Logger
.Error("Could not render view", ex
);
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
);
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
);
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;
297 ProcessLayout(contents
, controller
, controllerContext
, ctx
, context
, context
.Response
.Output
);
301 context
.Response
.Output
.Write(contents
);
308 /// Initializes basic velocity properties. The main purpose of this method is to
309 /// allow this logic to be overrided.
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
));
321 /// Resolves the layout template name into a velocity template file name.
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
);
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
=
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);