blob: 1da4e800995acf21ed4fdf69de6f8215b5a02584 [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.StringWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
/**
* Extension of the VelocityViewServlet to perform "two-pass"
* layout rendering and allow for a customized error screen.
*
* @author Nathan Bubna
* @version $Id$
*/
public class VelocityLayoutServlet extends VelocityViewServlet
{
/** serial version id */
private static final long serialVersionUID = -4521817395157483487L;
/**
* The velocity.properties key for specifying the
* servlet's error template.
*/
public static final String PROPERTY_ERROR_TEMPLATE =
"tools.view.servlet.error.template";
/**
* The velocity.properties key for specifying the
* relative directory holding layout templates.
*/
public static final String PROPERTY_LAYOUT_DIR =
"tools.view.servlet.layout.directory";
/**
* The velocity.properties key for specifying the
* servlet's default layout template's filename.
*/
public static final String PROPERTY_DEFAULT_LAYOUT =
"tools.view.servlet.layout.default.template";
/**
* The default error template's filename.
*/
public static final String DEFAULT_ERROR_TEMPLATE = "Error.vm";
/**
* The default layout directory
*/
public static final String DEFAULT_LAYOUT_DIR = "layout/";
/**
* The default filename for the servlet's default layout
*/
public static final String DEFAULT_DEFAULT_LAYOUT = "Default.vm";
/**
* The context key that will hold the content of the screen.
*
* This key ($screen_content) must be present in the layout
* template for the current screen to be rendered.
*/
public static final String KEY_SCREEN_CONTENT = "screen_content";
/**
* The context/parameter key used to specify an alternate
* layout to be used for a request instead of the default layout.
*/
public static final String KEY_LAYOUT = "layout";
/**
* The context key that holds the {@link Throwable} that
* broke the rendering of the requested screen.
*/
public static final String KEY_ERROR_CAUSE = "error_cause";
/**
* The context key that holds the stack trace of the error that
* broke the rendering of the requested screen.
*/
public static final String KEY_ERROR_STACKTRACE = "stack_trace";
/**
* The context key that holds the {@link MethodInvocationException}
* that broke the rendering of the requested screen.
*
* If this value is placed in the context, then $error_cause
* will hold the error that this invocation exception is wrapping.
*/
public static final String KEY_ERROR_INVOCATION_EXCEPTION = "invocation_exception";
protected String errorTemplate;
protected String layoutDir;
protected String defaultLayout;
/**
* Initializes Velocity, the view servlet and checks for changes to
* the initial layout configuration.
*
* @param config servlet configuration parameters
*/
public void init(ServletConfig config) throws ServletException
{
// first do VVS' init()
super.init(config);
// check for default template path overrides
errorTemplate =
getVelocityProperty(PROPERTY_ERROR_TEMPLATE, DEFAULT_ERROR_TEMPLATE);
layoutDir =
getVelocityProperty(PROPERTY_LAYOUT_DIR, DEFAULT_LAYOUT_DIR);
defaultLayout =
getVelocityProperty(PROPERTY_DEFAULT_LAYOUT, DEFAULT_DEFAULT_LAYOUT);
// preventive error checking! directory must end in /
if (!layoutDir.endsWith("/"))
{
layoutDir += '/';
}
// log the current settings
getLog().info("VelocityLayoutServlet: Error screen is '"+errorTemplate+"'");
getLog().info("VelocityLayoutServlet: Layout directory is '"+layoutDir+"'");
getLog().info("VelocityLayoutServlet: Default layout template is '"+defaultLayout+"'");
// for efficiency's sake, make defaultLayout a full path now
defaultLayout = layoutDir + defaultLayout;
}
/**
* Overrides VelocityViewServlet to check the request for
* an alternate layout
*
* @param ctx context for this request
* @param request client request
*/
protected void fillContext(Context ctx, HttpServletRequest request)
{
String layout = findLayout(request);
if (layout != null)
{
// let the template know what its new layout is
ctx.put(KEY_LAYOUT, layout);
}
}
/**
* Searches for a non-default layout to be used for this request.
* This implementation checks the request parameters and attributes.
*/
protected String findLayout(HttpServletRequest request)
{
// check if an alternate layout has been specified
// by way of the request parameters
String layout = request.getParameter(KEY_LAYOUT);
// also look in the request attributes
if (layout == null)
{
layout = (String)request.getAttribute(KEY_LAYOUT);
}
return layout;
}
/**
* Overrides VelocityViewServlet.mergeTemplate to do a two-pass
* render for handling layouts
*/
protected void mergeTemplate(Template template, Context context,
HttpServletResponse response)
throws IOException
{
//
// this section is based on Tim Colson's "two pass render"
//
// Render the screen content
StringWriter sw = new StringWriter();
template.merge(context, sw);
// Add the resulting content to the context
context.put(KEY_SCREEN_CONTENT, sw.toString());
// Check for an alternate layout
//
// we check after merging the screen template so the screen
// can overrule any layout set in the request parameters
// by doing #set( $layout = "MyLayout.vm" )
Object obj = context.get(KEY_LAYOUT);
String layout = (obj == null) ? null : obj.toString();
if (layout == null)
{
// no alternate, use default
layout = defaultLayout;
}
else
{
// make it a full(er) path
layout = layoutDir + layout;
}
try
{
//load the layout template
template = getTemplate(layout);
}
catch (Exception e)
{
getLog().error("Can't load layout \"" + layout + "\"", e);
// if it was an alternate layout we couldn't get...
if (!layout.equals(defaultLayout))
{
// try to get the default layout
// if this also fails, let the exception go
template = getTemplate(defaultLayout);
}
}
// Render the layout template into the response
super.mergeTemplate(template, context, response);
}
/**
* Overrides VelocityViewServlet to display user's custom error template
*/
protected void error(HttpServletRequest request,
HttpServletResponse response,
Throwable e)
{
try
{
// get a velocity context
Context ctx = createContext(request, response);
Throwable cause = e;
// if it's an MIE, i want the real cause and stack trace!
if (cause instanceof MethodInvocationException)
{
// put the invocation exception in the context
ctx.put(KEY_ERROR_INVOCATION_EXCEPTION, e);
// get the real cause
cause = ((MethodInvocationException)e).getWrappedThrowable();
}
// add the cause to the context
ctx.put(KEY_ERROR_CAUSE, cause);
// grab the cause's stack trace and put it in the context
StringWriter sw = new StringWriter();
cause.printStackTrace(new java.io.PrintWriter(sw));
ctx.put(KEY_ERROR_STACKTRACE, sw.toString());
// retrieve and render the error template
Template et = getTemplate(errorTemplate);
mergeTemplate(et, ctx, response);
}
catch (Exception e2)
{
// d'oh! log this
getLog().error("Error during error template rendering", e2);
// then punt the original to a higher authority
super.error(request, response, e);
}
}
}