1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.web.servlet.view.velocity;
  17. import java.util.Locale;
  18. import java.util.Map;
  19. import javax.servlet.http.HttpServletRequest;
  20. import javax.servlet.http.HttpServletResponse;
  21. import org.apache.velocity.Template;
  22. import org.apache.velocity.VelocityContext;
  23. import org.apache.velocity.app.VelocityEngine;
  24. import org.apache.velocity.app.tools.VelocityFormatter;
  25. import org.apache.velocity.context.Context;
  26. import org.apache.velocity.exception.ResourceNotFoundException;
  27. import org.apache.velocity.tools.generic.DateTool;
  28. import org.apache.velocity.tools.generic.NumberTool;
  29. import org.springframework.beans.BeansException;
  30. import org.springframework.beans.factory.BeanFactoryUtils;
  31. import org.springframework.beans.factory.NoSuchBeanDefinitionException;
  32. import org.springframework.context.ApplicationContextException;
  33. import org.springframework.web.servlet.support.RequestContextUtils;
  34. import org.springframework.web.servlet.view.AbstractTemplateView;
  35. /**
  36. * View using the Velocity template engine.
  37. * Based on code in the VelocityServlet shipped with Velocity.
  38. *
  39. * <p>Exposes the following JavaBean properties:
  40. * <ul>
  41. * <li><b>url</b>: the location of the Velocity template to be wrapped,
  42. * relative to the Velocity resource loader path (see VelocityConfigurer).
  43. * <li><b>encoding</b> (optional, default is determined by Velocity configuration):
  44. * the encoding of the Velocity template file
  45. * <li><b>velocityFormatterAttribute</b> (optional, default=null): the name of
  46. * the VelocityFormatter helper object to expose in the Velocity context of this
  47. * view, or null if not needed. VelocityFormatter is part of standard Velocity.
  48. * <li><b>dateToolAttribute</b> (optional, default=null): the name of the
  49. * DateTool helper object to expose in the Velocity context of this view,
  50. * or null if not needed. DateTool is part of Velocity Tools 1.0.
  51. * <li><b>numberToolAttribute</b> (optional, default=null): the name of the
  52. * NumberTool helper object to expose in the Velocity context of this view,
  53. * or null if not needed. NumberTool is part of Velocity Tools 1.1.
  54. * <li><b>cacheTemplate</b> (optional, default=false): whether or not the Velocity
  55. * template should be cached. It should normally be true in production, but setting
  56. * this to false enables us to modify Velocity templates without restarting the
  57. * application (similar to JSPs). Note that this is a minor optimization only,
  58. * as Velocity itself caches templates in a modification-aware fashion.
  59. * </ul>
  60. *
  61. * <p>Depends on a VelocityConfig object such as VelocityConfigurer being
  62. * accessible in the current web application context, with any bean name.
  63. * Alternatively, you can set the VelocityEngine object as bean property.
  64. *
  65. * @author Rod Johnson
  66. * @author Juergen Hoeller
  67. * @version $Id: VelocityView.java,v 1.27 2004/05/21 19:33:40 jhoeller Exp $
  68. * @see VelocityConfig
  69. * @see VelocityConfigurer
  70. * @see #setUrl
  71. * @see #setEncoding
  72. * @see #setVelocityEngine
  73. * @see VelocityConfig
  74. * @see VelocityConfigurer
  75. */
  76. public class VelocityView extends AbstractTemplateView {
  77. public static final int DEFAULT_WRITER_POOL_SIZE = 40;
  78. public static final int OUTPUT_BUFFER_SIZE = 4096;
  79. private String encoding = null;
  80. private String velocityFormatterAttribute;
  81. private String dateToolAttribute;
  82. private String numberToolAttribute;
  83. private boolean cacheTemplate;
  84. private VelocityEngine velocityEngine;
  85. private Template template;
  86. /**
  87. * Set the encoding of the Velocity template file. Default is determined
  88. * by the VelocityEngine: "ISO-8859-1" if not specified otherwise.
  89. * <p>Specify the encoding in the VelocityEngine rather than per template
  90. * if all your templates share a common encoding.
  91. */
  92. public void setEncoding(String encoding) {
  93. this.encoding = encoding;
  94. }
  95. /**
  96. * Return the encoding for the Velocity template.
  97. */
  98. protected String getEncoding() {
  99. return encoding;
  100. }
  101. /**
  102. * Set the name of the VelocityFormatter helper object to expose in the
  103. * Velocity context of this view, or null if not needed.
  104. * VelocityFormatter is part of the standard Velocity distribution.
  105. * @see org.apache.velocity.app.tools.VelocityFormatter
  106. */
  107. public void setVelocityFormatterAttribute(String velocityFormatterAttribute) {
  108. this.velocityFormatterAttribute = velocityFormatterAttribute;
  109. }
  110. /**
  111. * Set the name of the DateTool helper object to expose in the Velocity context
  112. * of this view, or null if not needed. DateTool is part of Velocity Tools 1.0.
  113. * @see org.apache.velocity.tools.generic.DateTool
  114. */
  115. public void setDateToolAttribute(String dateToolAttribute) {
  116. this.dateToolAttribute = dateToolAttribute;
  117. }
  118. /**
  119. * Set the name of the NumberTool helper object to expose in the Velocity context
  120. * of this view, or null if not needed. NumberTool is part of Velocity Tools 1.1.
  121. * @see org.apache.velocity.tools.generic.NumberTool
  122. */
  123. public void setNumberToolAttribute(String numberToolAttribute) {
  124. this.numberToolAttribute = numberToolAttribute;
  125. }
  126. /**
  127. * Set whether the Velocity template should be cached. Default is false.
  128. * It should normally be true in production, but setting this to false enables us to
  129. * modify Velocity templates without restarting the application (similar to JSPs).
  130. * <p>Note that this is a minor optimization only, as Velocity itself caches
  131. * templates in a modification-aware fashion.
  132. */
  133. public void setCacheTemplate(boolean cacheTemplate) {
  134. this.cacheTemplate = cacheTemplate;
  135. }
  136. /**
  137. * Set the VelocityEngine to be used by this view.
  138. * If this is not set, the default lookup will occur: A single VelocityConfig
  139. * is expected in the current web application context, with any bean name.
  140. * @see VelocityConfig
  141. */
  142. public void setVelocityEngine(VelocityEngine velocityEngine) {
  143. this.velocityEngine = velocityEngine;
  144. }
  145. /**
  146. * Return the VelocityEngine used by this view.
  147. */
  148. protected VelocityEngine getVelocityEngine() {
  149. return velocityEngine;
  150. }
  151. /**
  152. * Invoked on startup. Looks for a single VelocityConfig bean to
  153. * find the relevant VelocityEngine for this factory.
  154. */
  155. protected void initApplicationContext() throws BeansException {
  156. super.initApplicationContext();
  157. if (this.velocityEngine == null) {
  158. try {
  159. VelocityConfig velocityConfig = (VelocityConfig)
  160. BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(),
  161. VelocityConfig.class, true, true);
  162. this.velocityEngine = velocityConfig.getVelocityEngine();
  163. }
  164. catch (NoSuchBeanDefinitionException ex) {
  165. throw new ApplicationContextException("Must define a single VelocityConfig bean in this web application " +
  166. "context (may be inherited): VelocityConfigurer is the usual implementation. " +
  167. "This bean may be given any name.", ex);
  168. }
  169. }
  170. try {
  171. // check that we can get the template, even if we might subsequently get it again
  172. this.template = getTemplate();
  173. }
  174. catch (ResourceNotFoundException ex) {
  175. throw new ApplicationContextException("Cannot find Velocity template for URL [" + getUrl() +
  176. "]: Did you specify the correct resource loader path?", ex);
  177. }
  178. catch (Exception ex) {
  179. throw new ApplicationContextException("Cannot load Velocity template for URL [" + getUrl() + "]", ex);
  180. }
  181. }
  182. /**
  183. * Process the model map by merging it with the Velocity template. Output is
  184. * directed to the response. This method can be overridden if custom behavior
  185. * is needed.
  186. */
  187. protected void renderMergedTemplateModel(Map model, HttpServletRequest request,
  188. HttpServletResponse response) throws Exception {
  189. // We already hold a reference to the template, but we might want to load it
  190. // if not caching. As Velocity itself caches templates, so our ability to
  191. // cache templates in this class is a minor optimization only.
  192. Template template = this.template;
  193. if (!this.cacheTemplate) {
  194. template = getTemplate();
  195. }
  196. response.setContentType(getContentType());
  197. // create context from model and add Velocity helpers
  198. Context velocityContext = new VelocityContext(model);
  199. exposeHelpers(velocityContext, request);
  200. if (this.velocityFormatterAttribute != null) {
  201. velocityContext.put(this.velocityFormatterAttribute, new VelocityFormatter(velocityContext));
  202. }
  203. if (this.dateToolAttribute != null || this.numberToolAttribute != null) {
  204. Locale locale = RequestContextUtils.getLocale(request);
  205. if (this.dateToolAttribute != null) {
  206. velocityContext.put(this.dateToolAttribute, new LocaleAwareDateTool(locale));
  207. }
  208. if (this.numberToolAttribute != null) {
  209. velocityContext.put(this.numberToolAttribute, new LocaleAwareNumberTool(locale));
  210. }
  211. }
  212. mergeTemplate(template, velocityContext, response);
  213. if (logger.isDebugEnabled()) {
  214. logger.debug("Merged with Velocity template '" + getUrl() + "' in VelocityView '" + getBeanName() + "'");
  215. }
  216. }
  217. /**
  218. * Retrieve the Velocity template.
  219. * @return the Velocity template to process
  220. * @throws Exception if thrown by Velocity
  221. */
  222. protected Template getTemplate() throws Exception {
  223. return (this.encoding != null ? this.velocityEngine.getTemplate(getUrl(), this.encoding) :
  224. this.velocityEngine.getTemplate(getUrl()));
  225. }
  226. /**
  227. * Expose helpers unique to each rendering operation. This is necessary so that
  228. * different rendering operations can't overwrite each other's formats etc.
  229. * <p>Called by renderMergedOutputModel. The default implementations is empty.
  230. * This method can be overridden to add custom helpers to the Velocity context.
  231. * @param velocityContext Velocity context that will be passed to the template at merge time
  232. * @param request current HTTP request
  233. * @throws Exception if there's a fatal error while we're adding information to the context
  234. * @see #renderMergedOutputModel
  235. */
  236. protected void exposeHelpers(Context velocityContext, HttpServletRequest request) throws Exception {
  237. }
  238. /**
  239. * Merge the template with the context.
  240. * Can be overridden to customize the behavior.
  241. * @param template the template to merge
  242. * @param context the Velocity context
  243. * @param response servlet response (use this to get the OutputStream or Writer)
  244. * @see org.apache.velocity.Template#merge
  245. */
  246. protected void mergeTemplate(Template template, Context context, HttpServletResponse response) throws Exception {
  247. template.merge(context, response.getWriter());
  248. }
  249. /**
  250. * Subclass of DateTool from Velocity tools,
  251. * using the RequestContext Locale instead of the default Locale.
  252. * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
  253. */
  254. private static class LocaleAwareDateTool extends DateTool {
  255. private Locale locale;
  256. private LocaleAwareDateTool(Locale locale) {
  257. this.locale = locale;
  258. }
  259. public Locale getLocale() {
  260. return this.locale;
  261. }
  262. }
  263. /**
  264. * Subclass of NumberTool from Velocity tools,
  265. * using the RequestContext Locale instead of the default Locale.
  266. * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
  267. */
  268. private static class LocaleAwareNumberTool extends NumberTool {
  269. private Locale locale;
  270. private LocaleAwareNumberTool(Locale locale) {
  271. this.locale = locale;
  272. }
  273. public Locale getLocale() {
  274. return this.locale;
  275. }
  276. }
  277. }