001: package org.apache.velocity.tools.view.servlet;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.IOException;
023: import java.io.StringWriter;
024: import java.io.UnsupportedEncodingException;
025:
026: import javax.servlet.ServletConfig;
027: import javax.servlet.ServletException;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.velocity.Template;
032: import org.apache.velocity.app.VelocityEngine;
033: import org.apache.velocity.context.Context;
034: import org.apache.velocity.exception.MethodInvocationException;
035: import org.apache.velocity.exception.ParseErrorException;
036: import org.apache.velocity.exception.ResourceNotFoundException;
037:
038: /**
039: * Extension of the VelocityViewServlet to perform "two-pass"
040: * layout rendering and allow for a customized error screen.
041: *
042: * For a brief user-guide to this, i suggest trying to track down
043: * the VLS_README.txt file that hopefully made it into the docs
044: * somewhere.
045: *
046: * @author Nathan Bubna
047: * @version $Id: VelocityLayoutServlet.java 479724 2006-11-27 18:49:37Z nbubna $
048: */
049:
050: public class VelocityLayoutServlet extends VelocityViewServlet {
051:
052: /** serial version id */
053: private static final long serialVersionUID = -4521817395157483487L;
054:
055: /**
056: * The velocity.properties key for specifying the
057: * servlet's error template.
058: */
059: public static final String PROPERTY_ERROR_TEMPLATE = "tools.view.servlet.error.template";
060:
061: /**
062: * The velocity.properties key for specifying the
063: * relative directory holding layout templates.
064: */
065: public static final String PROPERTY_LAYOUT_DIR = "tools.view.servlet.layout.directory";
066:
067: /**
068: * The velocity.properties key for specifying the
069: * servlet's default layout template's filename.
070: */
071: public static final String PROPERTY_DEFAULT_LAYOUT = "tools.view.servlet.layout.default.template";
072:
073: /**
074: * The default error template's filename.
075: */
076: public static String DEFAULT_ERROR_TEMPLATE = "Error.vm";
077:
078: /**
079: * The default layout directory
080: */
081: public static String DEFAULT_LAYOUT_DIR = "layout/";
082:
083: /**
084: * The default filename for the servlet's default layout
085: */
086: public static String DEFAULT_DEFAULT_LAYOUT = "Default.vm";
087:
088: //TODO? if demand for it exists, these context keys can be
089: // made configurable by the velocity.properties
090: // until such time, if anyone really needs to change these,
091: // they are public and aren't final and can be altered
092:
093: /**
094: * The context key that will hold the content of the screen.
095: *
096: * This key ($screen_content) must be present in the layout
097: * template for the current screen to be rendered.
098: */
099: public static String KEY_SCREEN_CONTENT = "screen_content";
100:
101: /**
102: * The context/parameter key used to specify an alternate
103: * layout to be used for a request instead of the default layout.
104: */
105: public static String KEY_LAYOUT = "layout";
106:
107: /**
108: * The context key that holds the {@link Throwable} that
109: * broke the rendering of the requested screen.
110: */
111: public static String KEY_ERROR_CAUSE = "error_cause";
112:
113: /**
114: * The context key that holds the stack trace of the error that
115: * broke the rendering of the requested screen.
116: */
117: public static String KEY_ERROR_STACKTRACE = "stack_trace";
118:
119: /**
120: * The context key that holds the {@link MethodInvocationException}
121: * that broke the rendering of the requested screen.
122: *
123: * If this value is placed in the context, then $error_cause
124: * will hold the error that this invocation exception is wrapping.
125: */
126: public static String KEY_ERROR_INVOCATION_EXCEPTION = "invocation_exception";
127:
128: protected String errorTemplate;
129: protected String layoutDir;
130: protected String defaultLayout;
131:
132: // keep a local reference for convenience
133: private VelocityEngine velocity;
134:
135: /**
136: * Initializes Velocity, the view servlet and checks for changes to
137: * the initial layout configuration.
138: *
139: * @param config servlet configuration parameters
140: */
141: public void init(ServletConfig config) throws ServletException {
142: // first do VVS' init()
143: super .init(config);
144:
145: // grab the initialized engine
146: velocity = super .getVelocityEngine();
147:
148: // check for default template path overrides
149: errorTemplate = getVelocityProperty(PROPERTY_ERROR_TEMPLATE,
150: DEFAULT_ERROR_TEMPLATE);
151: layoutDir = getVelocityProperty(PROPERTY_LAYOUT_DIR,
152: DEFAULT_LAYOUT_DIR);
153: defaultLayout = getVelocityProperty(PROPERTY_DEFAULT_LAYOUT,
154: DEFAULT_DEFAULT_LAYOUT);
155:
156: // preventive error checking! directory must end in /
157: if (!layoutDir.endsWith("/")) {
158: layoutDir += '/';
159: }
160:
161: // log the current settings
162: velocity.info("VelocityLayoutServlet: Error screen is '"
163: + errorTemplate + "'");
164: velocity.info("VelocityLayoutServlet: Layout directory is '"
165: + layoutDir + "'");
166: velocity
167: .info("VelocityLayoutServlet: Default layout template is '"
168: + defaultLayout + "'");
169:
170: // for efficiency's sake, make defaultLayout a full path now
171: defaultLayout = layoutDir + defaultLayout;
172: }
173:
174: /**
175: * Overrides VelocityViewServlet to check the request for
176: * an alternate layout
177: *
178: * @param request client request
179: * @param response client response
180: * @return the Context to fill
181: */
182: protected Context createContext(HttpServletRequest request,
183: HttpServletResponse response) {
184:
185: Context ctx = super .createContext(request, response);
186:
187: // check if an alternate layout has been specified
188: // by way of the request parameters
189: String layout = request.getParameter(KEY_LAYOUT);
190: if (layout != null) {
191: // let the template know what its new layout is
192: ctx.put(KEY_LAYOUT, layout);
193: }
194: return ctx;
195: }
196:
197: /**
198: * Overrides VelocityViewServlet.mergeTemplate to do a two-pass
199: * render for handling layouts
200: */
201: protected void mergeTemplate(Template template, Context context,
202: HttpServletResponse response)
203: throws ResourceNotFoundException, ParseErrorException,
204: MethodInvocationException, IOException,
205: UnsupportedEncodingException, Exception {
206: //
207: // this section is based on Tim Colson's "two pass render"
208: //
209: // Render the screen content
210: StringWriter sw = new StringWriter();
211: template.merge(context, sw);
212: // Add the resulting content to the context
213: context.put(KEY_SCREEN_CONTENT, sw.toString());
214:
215: // Check for an alternate layout
216: //
217: // we check after merging the screen template so the screen
218: // can overrule any layout set in the request parameters
219: // by doing #set( $layout = "MyLayout.vm" )
220: Object obj = context.get(KEY_LAYOUT);
221: String layout = (obj == null) ? null : obj.toString();
222: if (layout == null) {
223: // no alternate, use default
224: layout = defaultLayout;
225: } else {
226: // make it a full(er) path
227: layout = layoutDir + layout;
228: }
229:
230: try {
231: //load the layout template
232: template = getTemplate(layout);
233: } catch (Exception e) {
234: velocity
235: .error("VelocityLayoutServlet: Can't load layout \""
236: + layout + "\": " + e);
237:
238: // if it was an alternate layout we couldn't get...
239: if (!layout.equals(defaultLayout)) {
240: // try to get the default layout
241: // if this also fails, let the exception go
242: template = getTemplate(defaultLayout);
243: }
244: }
245:
246: // Render the layout template into the response
247: super .mergeTemplate(template, context, response);
248: }
249:
250: /**
251: * Overrides VelocityViewServlet to display user's custom error template
252: */
253: protected void error(HttpServletRequest request,
254: HttpServletResponse response, Exception e)
255: throws ServletException {
256: try {
257: // get a velocity context
258: Context ctx = createContext(request, response);
259:
260: Throwable cause = e;
261:
262: // if it's an MIE, i want the real cause and stack trace!
263: if (cause instanceof MethodInvocationException) {
264: // put the invocation exception in the context
265: ctx.put(KEY_ERROR_INVOCATION_EXCEPTION, e);
266: // get the real cause
267: cause = ((MethodInvocationException) e)
268: .getWrappedThrowable();
269: }
270:
271: // add the cause to the context
272: ctx.put(KEY_ERROR_CAUSE, cause);
273:
274: // grab the cause's stack trace and put it in the context
275: StringWriter sw = new StringWriter();
276: cause.printStackTrace(new java.io.PrintWriter(sw));
277: ctx.put(KEY_ERROR_STACKTRACE, sw.toString());
278:
279: // retrieve and render the error template
280: Template et = getTemplate(errorTemplate);
281: mergeTemplate(et, ctx, response);
282:
283: } catch (Exception e2) {
284: // d'oh! log this
285: velocity.error("VelocityLayoutServlet: "
286: + " Error during error template rendering - " + e2);
287: // then punt the original to a higher authority
288: super.error(request, response, e);
289: }
290: }
291:
292: }
|