blob: 89e89386eb4001070249474a44715a44f0e1c420 [file] [log] [blame]
package org.apache.velocity.tools.view;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.log.Log;
/**
* <p>A servlet to process Velocity templates. This is comparable to the
* the JspServlet for JSP-based applications.</p>
*
* <p>The servlet provides the following features:</p>
* <ul>
* <li>renders Velocity templates</li>
* <li>provides support for an auto-loaded, configurable toolbox</li>
* <li>provides transparent access to the servlet request attributes,
* servlet session attributes and servlet context attributes by
* auto-searching them</li>
* <li>logs to the logging facility of the servlet API</li>
* </ul>
*
* <p>VelocityViewServlet supports the following configuration parameters
* in web.xml:</p>
* <dl>
* <dt>org.apache.velocity.tools</dt>
* <dd>Path and name of the toolbox configuration file. The path must be
* relative to the web application root directory. If this parameter is
* not found, the servlet will check for a toolbox file at
* '/WEB-INF/tools.xml'.</dd>
* <dt>org.apache.velocity.properties</dt>
* <dd>Path and name of the Velocity configuration file. The path must be
* relative to the web application root directory. If this parameter
* is not present, Velocity will check for a properties file at
* '/WEB-INF/velocity.properties'. If no file is found there, then
* Velocity is initialized with the settings in the classpath at
* 'org.apache.velocity.tools.view.velocity.properties'.</dd>
* <dt>org.apache.velocity.tools.shared.config</dt>
* <dd>By default, this is {@code true}. If set to {@code false}, then
* the {@link VelocityView} used by this servlet will not be shared
* with {@link VelocityViewFilter}s, other VelocityViewServlets or
* {@link org.apache.velocity.tools.view.jsp.VelocityViewTag}s in the
* application.</dd>
* <dt>org.apache.velocity.tools.loadDefaults</dt>
* <dd>By default, this is {@code true}. If set to {@code false}, then
* the default toolbox configuration will not be added to your (if any)
* custom configuration. NOTE: The default configuration will also be
* suppressed if you are using a deprecated toolbox.xml format and do not
* explicitly set this to {@code true}.</dd>
* <dt>org.apache.velocity.tools.cleanConfiguration</dt>
* <dd>By default, this is {@code false}. If set to {@code true}, then
* then the final toolbox configuration (the combination of any custom
* one(s) provided by yourself and/or the default configuration(s))
* will have all invalid tools, properties, and/or data removed prior to
* configuring the ToolboxFactory for this servlet by a
* {@link org.apache.velocity.tools.config.ConfigurationCleaner}</dd>
* <dt>org.apache.velocity.tools.bufferOutput</dt>
* <dd>By default, the processed templates are merged directly into
* the {@link HttpServletResponse}'s writer. If this parameter is
* set to {@code true}, then the output of the merge process will be
* buffered before being fed to the response. This allows the {@link #error}
* method to be overridden to return a "500 Internal Server Error" or
* at least not return any of the failed request content. Essentially,
* setting this to {@code true} degrades performance in order to enable
* a more "correct" error response"</dd>
* </dd>
* </dl>
*
* @version $Id$
*/
public class VelocityViewServlet extends HttpServlet
{
public static final String BUFFER_OUTPUT_PARAM =
"org.apache.velocity.tools.bufferOutput";
private static final long serialVersionUID = -3329444102562079189L;
private transient VelocityView view;
private boolean bufferOutput = false;
/**
* <p>Initializes servlet and VelocityView used to process requests.
* Called by the servlet container on loading.</p>
*
* @param config servlet configuation
*/
public void init(ServletConfig config) throws ServletException
{
super.init(config);
// init the VelocityView (if it hasn't been already)
getVelocityView();
String buffer = findInitParameter(config, BUFFER_OUTPUT_PARAM);
if (buffer != null && buffer.equals("true"))
{
this.bufferOutput = true;
getLog().debug("VelocityViewServlet will buffer mergeTemplate output.");
}
}
/**
* Looks up an init parameter with the specified key in either the
* ServletConfig or, failing that, in the ServletContext.
*/
protected String findInitParameter(ServletConfig config, String key)
{
// check the servlet config
String param = config.getInitParameter(key);
if (param == null || param.length() == 0)
{
// check the servlet context
ServletContext servletContext = config.getServletContext();
param = servletContext.getInitParameter(key);
}
return param;
}
protected VelocityView getVelocityView()
{
if (this.view == null)
{
setVelocityView(ServletUtils.getVelocityView(getServletConfig()));
assert (this.view != null);
}
return this.view;
}
protected void setVelocityView(VelocityView view)
{
this.view = view;
}
protected String getVelocityProperty(String name, String alternate)
{
return getVelocityView().getProperty(name, alternate);
}
protected Log getLog()
{
return getVelocityView().getLog();
}
/**
* Handles GET - calls doRequest()
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doRequest(request, response);
}
/**
* Handle a POST request - calls doRequest()
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doRequest(request, response);
}
/**
* Handles with both GET and POST requests
*
* @param request HttpServletRequest object containing client request
* @param response HttpServletResponse object for the response
*/
protected void doRequest(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
Context context = null;
try
{
// then get a context
context = createContext(request, response);
// call standard extension point
fillContext(context, request);
setContentType(request, response);
// get the template
Template template = handleRequest(request, response, context);
// merge the template and context into the response
mergeTemplate(template, context, response);
} catch (IOException e) {
error(request, response, e);
throw e;
}
catch (ResourceNotFoundException e)
{
manageResourceNotFound(request, response, e);
}
catch (RuntimeException e)
{
error(request, response, e);
throw e;
}
finally
{
requestCleanup(request, response, context);
}
}
/**
* <p>This was a common extension point, but now it is usually
* simpler to override {@link #fillContext} to add custom things
* to the {@link Context} or override a {@link #getTemplate}
* method to change how {@link Template}s are retrieved.
* This is only recommended for more complicated use-cases.</p>
*
* @param request client request
* @param response client response
* @param ctx VelocityContext to fill
* @return Velocity Template object or null
*/
protected Template handleRequest(HttpServletRequest request,
HttpServletResponse response,
Context ctx)
{
return getTemplate(request, response);
}
protected Context createContext(HttpServletRequest request,
HttpServletResponse response)
{
return getVelocityView().createContext(request, response);
}
protected void fillContext(Context context, HttpServletRequest request)
{
// this implementation does nothing
}
/**
* Sets the content type of the response. This is available to be overriden
* by a derived class.
*
* <p>The default implementation is :
* <code>
* response.setContentType(getVelocityView().getDefaultContentType());
* </code>
* where defaultContentType is set to the value of the default.contentType
* property, or "text/html" if that was not set for the {@link VelocityView}.
* </p>
*
* @param request servlet request from client
* @param response servlet reponse to client
*/
protected void setContentType(HttpServletRequest request,
HttpServletResponse response)
{
response.setContentType(getVelocityView().getDefaultContentType());
}
protected Template getTemplate(HttpServletRequest request,
HttpServletResponse response)
{
return getVelocityView().getTemplate(request, response);
}
protected Template getTemplate(String name)
{
return getVelocityView().getTemplate(name);
}
protected void mergeTemplate(Template template, Context context,
HttpServletResponse response)
throws IOException
{
Writer writer;
if (this.bufferOutput)
{
writer = new StringWriter();
}
else
{
writer = response.getWriter();
}
getVelocityView().merge(template, context, writer);
if (this.bufferOutput)
{
response.getWriter().write(writer.toString());
}
}
/**
* Invoked when there is an error thrown in any part of doRequest() processing.
* <br><br>
* Default will send a simple HTML response indicating there was a problem.
*
* @param request original HttpServletRequest from servlet container.
* @param response HttpServletResponse object from servlet container.
* @param e Exception that was thrown by some other part of process.
*/
protected void error(HttpServletRequest request,
HttpServletResponse response,
Throwable e)
{
if (!response.isCommitted())
{
return;
}
try
{
String path = ServletUtils.getPath(request);
getLog().error("Error processing a template for path '" + path + "'", e);
StringBuilder html = new StringBuilder();
html.append("<html>\n");
html.append("<head><title>Error</title></head>\n");
html.append("<body>\n");
html.append("<h2>VelocityView : Error processing a template for path '");
html.append(path);
html.append("'</h2>\n");
Throwable cause = e;
String why = cause.getMessage();
if (why != null && why.length() > 0)
{
html.append(StringEscapeUtils.escapeHtml(why));
html.append("\n<br>\n");
}
//TODO: add line/column/template info for parse errors et al
// if it's an MIE, i want the real stack trace!
if (cause instanceof MethodInvocationException)
{
// get the real cause
cause = ((MethodInvocationException)cause).getWrappedThrowable();
}
StringWriter sw = new StringWriter();
cause.printStackTrace(new PrintWriter(sw));
html.append("<pre>\n");
html.append(StringEscapeUtils.escapeHtml(sw.toString()));
html.append("</pre>\n");
html.append("</body>\n");
html.append("</html>");
response.getWriter().write(html.toString());
}
catch (Exception e2)
{
// clearly something is quite wrong.
// let's log the new exception then give up and
// throw a runtime exception that wraps the first one
String msg = "Exception while printing error screen";
getLog().error(msg, e2);
throw new RuntimeException(msg, e);
}
}
/**
* Manages the {@link ResourceNotFoundException} to send an HTTP 404 result
* when needed.
*
* @param request The request object.
* @param response The response object.
* @param e The exception to check.
* @throws IOException If something goes wrong when sending the HTTP error.
*/
protected void manageResourceNotFound(HttpServletRequest request,
HttpServletResponse response, ResourceNotFoundException e)
throws IOException
{
String path = ServletUtils.getPath(request);
if (getLog().isDebugEnabled())
{
getLog().debug("Resource not found for path '" + path + "'", e);
}
String message = e.getMessage();
if (!response.isCommitted() && path != null &&
message != null && message.contains("'" + path + "'"))
{
response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
}
else
{
error(request, response, e);
throw e;
}
}
/**
* Cleanup routine called at the end of the request processing sequence
* allows a derived class to do resource cleanup or other end of
* process cycle tasks. This default implementation does nothing.
*
* @param request servlet request from client
* @param response servlet response
* @param context Context that was merged with the requested template
*/
protected void requestCleanup(HttpServletRequest request,
HttpServletResponse response,
Context context)
{
}
}