View Javadoc

1   package org.apache.velocity.tools.view;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  import java.io.StringWriter;
25  import java.io.Writer;
26  import javax.servlet.ServletConfig;
27  import javax.servlet.ServletContext;
28  import javax.servlet.ServletException;
29  import javax.servlet.http.HttpServlet;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import org.apache.commons.lang.StringEscapeUtils;
33  import org.apache.velocity.Template;
34  import org.apache.velocity.context.Context;
35  import org.apache.velocity.exception.MethodInvocationException;
36  import org.apache.velocity.exception.ResourceNotFoundException;
37  import org.apache.velocity.runtime.log.Log;
38  
39  /**
40   * <p>A servlet to process Velocity templates. This is comparable to the
41   * the JspServlet for JSP-based applications.</p>
42   *
43   * <p>The servlet provides the following features:</p>
44   * <ul>
45   *   <li>renders Velocity templates</li>
46   *   <li>provides support for an auto-loaded, configurable toolbox</li>
47   *   <li>provides transparent access to the servlet request attributes,
48   *       servlet session attributes and servlet context attributes by
49   *       auto-searching them</li>
50   *   <li>logs to the logging facility of the servlet API</li>
51   * </ul>
52   *
53   * <p>VelocityViewServlet supports the following configuration parameters
54   * in web.xml:</p>
55   * <dl>
56   *   <dt>org.apache.velocity.tools</dt>
57   *   <dd>Path and name of the toolbox configuration file. The path must be
58   *     relative to the web application root directory. If this parameter is
59   *     not found, the servlet will check for a toolbox file at
60   *     '/WEB-INF/tools.xml'.</dd>
61   *   <dt>org.apache.velocity.properties</dt>
62   *   <dd>Path and name of the Velocity configuration file. The path must be
63   *     relative to the web application root directory. If this parameter
64   *     is not present, Velocity will check for a properties file at
65   *     '/WEB-INF/velocity.properties'.  If no file is found there, then
66   *     Velocity is initialized with the settings in the classpath at
67   *     'org.apache.velocity.tools.view.velocity.properties'.</dd>
68   *   <dt>org.apache.velocity.tools.shared.config</dt>
69   *   <dd>By default, this is {@code true}. If set to {@code false}, then
70   *     the {@link VelocityView} used by this servlet will not be shared
71   *     with {@link VelocityViewFilter}s, other VelocityViewServlets or 
72   *     {@link org.apache.velocity.tools.view.jsp.VelocityViewTag}s in the
73   *     application.</dd>
74   *   <dt>org.apache.velocity.tools.loadDefaults</dt>
75   *   <dd>By default, this is {@code true}. If set to {@code false}, then
76   *     the default toolbox configuration will not be added to your (if any)
77   *     custom configuration.  NOTE: The default configuration will also be
78   *     suppressed if you are using a deprecated toolbox.xml format and do not
79   *     explicitly set this to {@code true}.</dd>
80   *   <dt>org.apache.velocity.tools.cleanConfiguration</dt>
81   *   <dd>By default, this is {@code false}. If set to {@code true}, then
82   *     then the final toolbox configuration (the combination of any custom
83   *     one(s) provided by yourself and/or the default configuration(s))
84   *     will have all invalid tools, properties, and/or data removed prior to
85   *     configuring the ToolboxFactory for this servlet by a
86   *     {@link org.apache.velocity.tools.config.ConfigurationCleaner}</dd>
87   *   <dt>org.apache.velocity.tools.bufferOutput</dt>
88   *   <dd>By default, the processed templates are merged directly into
89   *     the {@link HttpServletResponse}'s writer.  If this parameter is
90   *     set to {@code true}, then the output of the merge process will be
91   *     buffered before being fed to the response. This allows the {@link #error}
92   *     method to be overridden to return a "500 Internal Server Error" or
93   *     at least not return any of the failed request content. Essentially,
94   *     setting this to {@code true} degrades performance in order to enable
95   *     a more "correct" error response"</dd>
96   *  </dd>
97   * </dl>
98   *
99   * @version $Id: VelocityViewServlet.java 791530 2009-07-06 16:06:52Z nbubna $
100  */
101 
102 public class VelocityViewServlet extends HttpServlet
103 {
104     public static final String BUFFER_OUTPUT_PARAM =
105         "org.apache.velocity.tools.bufferOutput";
106     private static final long serialVersionUID = -3329444102562079189L;
107 
108     private transient VelocityView view;
109     private boolean bufferOutput = false;
110 
111     /**
112      * <p>Initializes servlet and VelocityView used to process requests.
113      * Called by the servlet container on loading.</p>
114      *
115      * @param config servlet configuation
116      */
117     public void init(ServletConfig config) throws ServletException
118     {
119         super.init(config);
120 
121         // init the VelocityView (if it hasn't been already)
122         getVelocityView();
123 
124         String buffer = findInitParameter(config, BUFFER_OUTPUT_PARAM);
125         if (buffer != null && buffer.equals("true"))
126         {
127             this.bufferOutput = true;
128             getLog().debug("VelocityViewServlet will buffer mergeTemplate output.");
129         }
130     }
131 
132 
133     /**
134      * Looks up an init parameter with the specified key in either the
135      * ServletConfig or, failing that, in the ServletContext.
136      */
137     protected String findInitParameter(ServletConfig config, String key)
138     {
139         // check the servlet config
140         String param = config.getInitParameter(key);
141 
142         if (param == null || param.length() == 0)
143         {
144             // check the servlet context
145             ServletContext servletContext = config.getServletContext();
146             param = servletContext.getInitParameter(key);
147         }
148         return param;
149     }
150 
151     protected VelocityView getVelocityView()
152     {
153         if (this.view == null)
154         {
155             setVelocityView(ServletUtils.getVelocityView(getServletConfig()));
156             assert (this.view != null);
157         }
158         return this.view;
159     }
160 
161     protected void setVelocityView(VelocityView view)
162     {
163         this.view = view;
164     }
165 
166     protected String getVelocityProperty(String name, String alternate)
167     {
168         return getVelocityView().getProperty(name, alternate);
169     }
170 
171     protected Log getLog()
172     {
173         return getVelocityView().getLog();
174     }
175 
176     /**
177      * Handles GET - calls doRequest()
178      */
179     public void doGet(HttpServletRequest request, HttpServletResponse response)
180         throws ServletException, IOException
181     {
182         doRequest(request, response);
183     }
184 
185 
186     /**
187      * Handle a POST request - calls doRequest()
188      */
189     public void doPost(HttpServletRequest request, HttpServletResponse response)
190         throws ServletException, IOException
191     {
192         doRequest(request, response);
193     }
194 
195 
196     /**
197      *  Handles with both GET and POST requests
198      *
199      *  @param request  HttpServletRequest object containing client request
200      *  @param response HttpServletResponse object for the response
201      */
202     protected void doRequest(HttpServletRequest request, HttpServletResponse response)
203         throws IOException
204     {
205         Context context = null;
206         try
207         {
208             // then get a context
209             context = createContext(request, response);
210 
211             // call standard extension point
212             fillContext(context, request);
213 
214             setContentType(request, response);
215 
216             // get the template
217             Template template = handleRequest(request, response, context);
218 
219             // merge the template and context into the response
220             mergeTemplate(template, context, response);
221         } catch (IOException e) {
222             error(request, response, e);
223             throw e;
224         }
225         catch (ResourceNotFoundException e)
226         {
227             manageResourceNotFound(request, response, e);
228         }
229         catch (RuntimeException e)
230         {
231             error(request, response, e);
232             throw e;
233         }
234         finally
235         {
236             requestCleanup(request, response, context);
237         }
238     }
239 
240 
241     /**
242      * <p>This was a common extension point, but now it is usually
243      * simpler to override {@link #fillContext} to add custom things
244      * to the {@link Context} or override a {@link #getTemplate}
245      * method to change how {@link Template}s are retrieved.
246      * This is only recommended for more complicated use-cases.</p>
247      *
248      * @param request client request
249      * @param response client response
250      * @param ctx  VelocityContext to fill
251      * @return Velocity Template object or null
252      */
253     protected Template handleRequest(HttpServletRequest request,
254                                      HttpServletResponse response,
255                                      Context ctx)
256     {
257         return getTemplate(request, response);
258     }
259 
260     protected Context createContext(HttpServletRequest request,
261                                     HttpServletResponse response)
262     {
263         return getVelocityView().createContext(request, response);
264     }
265 
266     protected void fillContext(Context context, HttpServletRequest request)
267     {
268         // this implementation does nothing
269     }
270 
271 
272     /**
273      * Sets the content type of the response.  This is available to be overriden
274      * by a derived class.
275      *
276      * <p>The default implementation is :
277      * <code>
278      *    response.setContentType(getVelocityView().getDefaultContentType());
279      * </code>
280      * where defaultContentType is set to the value of the default.contentType
281      * property, or "text/html" if that was not set for the {@link VelocityView}.
282      * </p>
283      *
284      * @param request servlet request from client
285      * @param response servlet reponse to client
286      */
287     protected void setContentType(HttpServletRequest request,
288                                   HttpServletResponse response)
289     {
290         response.setContentType(getVelocityView().getDefaultContentType());
291     }
292 
293     protected Template getTemplate(HttpServletRequest request,
294                                    HttpServletResponse response)
295     {
296         return getVelocityView().getTemplate(request, response);
297     }
298 
299     protected Template getTemplate(String name)
300     {
301         return getVelocityView().getTemplate(name);
302     }
303 
304     protected void mergeTemplate(Template template, Context context,
305                                  HttpServletResponse response)
306         throws IOException
307     {
308         Writer writer;
309         if (this.bufferOutput)
310         {
311             writer = new StringWriter();
312         }
313         else
314         {
315             writer = response.getWriter();
316         }
317 
318         getVelocityView().merge(template, context, writer);
319 
320         if (this.bufferOutput)
321         {
322             response.getWriter().write(writer.toString());
323         }
324     }
325 
326 
327     /**
328      * Invoked when there is an error thrown in any part of doRequest() processing.
329      * <br><br>
330      * Default will send a simple HTML response indicating there was a problem.
331      *
332      * @param request original HttpServletRequest from servlet container.
333      * @param response HttpServletResponse object from servlet container.
334      * @param e  Exception that was thrown by some other part of process.
335      */
336     protected void error(HttpServletRequest request,
337                          HttpServletResponse response,
338                          Throwable e)
339     {
340         if (!response.isCommitted())
341         {
342             return;
343         }
344         
345         try
346         {
347             String path = ServletUtils.getPath(request);
348             getLog().error("Error processing a template for path '" + path + "'", e);
349             StringBuilder html = new StringBuilder();
350             html.append("<html>\n");
351             html.append("<head><title>Error</title></head>\n");
352             html.append("<body>\n");
353             html.append("<h2>VelocityView : Error processing a template for path '");
354             html.append(path);
355             html.append("'</h2>\n");
356 
357             Throwable cause = e;
358 
359             String why = cause.getMessage();
360             if (why != null && why.length() > 0)
361             {
362                 html.append(StringEscapeUtils.escapeHtml(why));
363                 html.append("\n<br>\n");
364             }
365 
366             //TODO: add line/column/template info for parse errors et al
367 
368             // if it's an MIE, i want the real stack trace!
369             if (cause instanceof MethodInvocationException)
370             {
371                 // get the real cause
372                 cause = ((MethodInvocationException)cause).getWrappedThrowable();
373             }
374 
375             StringWriter sw = new StringWriter();
376             cause.printStackTrace(new PrintWriter(sw));
377 
378             html.append("<pre>\n");
379             html.append(StringEscapeUtils.escapeHtml(sw.toString()));
380             html.append("</pre>\n");
381             html.append("</body>\n");
382             html.append("</html>");
383             response.getWriter().write(html.toString());
384         }
385         catch (Exception e2)
386         {
387             // clearly something is quite wrong.
388             // let's log the new exception then give up and
389             // throw a runtime exception that wraps the first one
390             String msg = "Exception while printing error screen";
391             getLog().error(msg, e2);
392             throw new RuntimeException(msg, e);
393         }
394     }
395 
396     /**
397      * Manages the {@link ResourceNotFoundException} to send an HTTP 404 result
398      * when needed.
399      * 
400      * @param request The request object.
401      * @param response The response object.
402      * @param e The exception to check.
403      * @throws IOException If something goes wrong when sending the HTTP error.
404      */
405     protected void manageResourceNotFound(HttpServletRequest request,
406             HttpServletResponse response, ResourceNotFoundException e)
407             throws IOException
408     {
409         String path = ServletUtils.getPath(request);
410         if (getLog().isDebugEnabled())
411         {
412             getLog().debug("Resource not found for path '" + path + "'", e);
413         }
414         String message = e.getMessage();
415         if (!response.isCommitted() && path != null &&
416             message != null && message.contains("'" + path + "'"))
417         {
418             response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
419         }
420         else
421         {
422             error(request, response, e);
423             throw e;
424         }
425     }
426 
427     /**
428      * Cleanup routine called at the end of the request processing sequence
429      * allows a derived class to do resource cleanup or other end of
430      * process cycle tasks.  This default implementation does nothing.
431      *
432      * @param request servlet request from client
433      * @param response servlet response
434      * @param context Context that was merged with the requested template
435      */
436     protected void requestCleanup(HttpServletRequest request,
437                                   HttpServletResponse response,
438                                   Context context)
439     {
440     }
441 
442 }