1 // Copyright 2004-2007 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
;
26 using Castle
.MonoRail
.Framework
.Helpers
;
27 using Castle
.MonoRail
.Framework
.Views
.NVelocity
.JSGeneration
;
28 using Commons
.Collections
;
31 /// Implements a view engine using the popular Velocity syntax.
33 /// For details on the syntax, check the VTL Reference Guide
34 /// http://jakarta.apache.org/velocity/docs/vtl-reference-guide.html
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();
49 /// Creates a new <see cref="NVelocityViewEngine"/> instance.
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())
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
);
82 #region IServiceEnabledComponent implementation
84 public override void Service(IServiceProvider provider
)
86 base.Service(provider
);
88 this.provider
= provider
;
93 #region IViewEngine implementation
96 /// Gets a value indicating whether the view engine
97 /// support the generation of JS.
100 /// <c>true</c> if JS generation is supported; otherwise, <c>false</c>.
102 public override bool SupportsJSGeneration
108 /// Gets the view file extension.
110 /// <value>The view file extension.</value>
111 public override string ViewFileExtension
113 get { return ".vm"; }
117 /// Gets the JS generator file extension.
119 /// <value>The JS generator file extension.</value>
120 public override string JSGeneratorFileExtension
122 get { return ".njs"; }
126 /// Processes the specified context.
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;
145 // Because we are rendering within a layout we need to catch it first
146 writer
= new StringWriter();
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
);
167 String contents
= (writer
as StringWriter
).GetStringBuilder().ToString();
168 ProcessLayout(contents
, controller
, ctx
, context
);
173 if (Logger
.IsErrorEnabled
)
175 Logger
.Error("Could not render view", ex
);
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!
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
);
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
);
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
,
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
);
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;
282 ProcessLayout(contents
, controller
, ctx
, context
);
286 context
.Response
.Output
.Write(contents
);
293 /// Initializes basic velocity properties. The main purpose of this method is to
294 /// allow this logic to be overrided.
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
));
306 /// Resolves the layout template name into a velocity template file name.
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
);
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
);
356 if (context
.Request
.IsLocal
)
358 SendErrorDetails(ex
, context
.Response
.Output
);
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
=
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);