001: package org.apache.velocity.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.FileNotFoundException;
023: import java.io.IOException;
024: import java.io.OutputStreamWriter;
025: import java.io.PrintWriter;
026: import java.io.StringWriter;
027: import java.io.UnsupportedEncodingException;
028: import java.util.Properties;
029:
030: import javax.servlet.ServletConfig;
031: import javax.servlet.ServletContext;
032: import javax.servlet.ServletException;
033: import javax.servlet.ServletOutputStream;
034: import javax.servlet.http.HttpServlet;
035: import javax.servlet.http.HttpServletRequest;
036: import javax.servlet.http.HttpServletResponse;
037:
038: import org.apache.velocity.Template;
039: import org.apache.velocity.VelocityContext;
040: import org.apache.velocity.app.Velocity;
041: import org.apache.velocity.context.Context;
042: import org.apache.velocity.exception.MethodInvocationException;
043: import org.apache.velocity.exception.ParseErrorException;
044: import org.apache.velocity.exception.ResourceNotFoundException;
045: import org.apache.velocity.io.VelocityWriter;
046: import org.apache.velocity.runtime.RuntimeConstants;
047: import org.apache.velocity.runtime.RuntimeSingleton;
048: import org.apache.velocity.util.SimplePool;
049:
050: /**
051: * Base class which simplifies the use of Velocity with Servlets.
052: * Extend this class, implement the <code>handleRequest()</code> method,
053: * and add your data to the context. Then call
054: * <code>getTemplate("myTemplate.wm")</code>.
055: *
056: * This class puts some things into the context object that you should
057: * be aware of:
058: * <pre>
059: * "req" - The HttpServletRequest object
060: * "res" - The HttpServletResponse object
061: * </pre>
062: *
063: * There are other methods you can override to access, alter or control
064: * any part of the request processing chain. Please see the javadocs for
065: * more information on :
066: * <ul>
067: * <li> loadConfiguration() : for setting up the Velocity runtime
068: * <li> createContext() : for creating and loading the Context
069: * <li> setContentType() : for changing the content type on a request
070: * by request basis
071: * <li> handleRequest() : you <b>must</b> implement this
072: * <li> mergeTemplate() : the template rendering process
073: * <li> requestCleanup() : post rendering resource or other cleanup
074: * <li> error() : error handling
075: * </ul>
076: * <br>
077: * If you put a String with key "contentType" object into the context within either your
078: * servlet or within your template, then that will be used to override
079: * the default content type specified in the properties file.
080: *
081: * @deprecated This servlet has been replaced by VelocityViewServlet,
082: * available from the Velocity-Tools sub-project. VelocityViewServlet
083: * provides support for quick, clean MVC web development.
084: * VelocityServlet will be removed in a future version of Velocity.
085: *
086: * @author Dave Bryson
087: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
088: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
089: * @author <a href="kjohnson@transparent.com">Kent Johnson</a>
090: * @author <a href="dlr@finemaltcoding.com">Daniel Rall</a>
091: * $Id: VelocityServlet.java 463298 2006-10-12 16:10:32Z henning $
092: */
093: public abstract class VelocityServlet extends HttpServlet {
094: /**
095: * The context key for the HTTP request object.
096: */
097: public static final String REQUEST = "req";
098:
099: /**
100: * The context key for the HTTP response object.
101: */
102: public static final String RESPONSE = "res";
103:
104: /**
105: * The HTTP content type context key.
106: */
107: public static final String CONTENT_TYPE = "default.contentType";
108:
109: /**
110: * The default content type for the response
111: */
112: public static final String DEFAULT_CONTENT_TYPE = "text/html";
113:
114: /**
115: * Encoding for the output stream
116: */
117: public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1";
118:
119: /**
120: * The default content type, itself defaulting to {@link
121: * #DEFAULT_CONTENT_TYPE} if not configured.
122: */
123: private static String defaultContentType;
124:
125: /**
126: * This is the string that is looked for when getInitParameter is
127: * called (<code>org.apache.velocity.properties</code>).
128: */
129: protected static final String INIT_PROPS_KEY = "org.apache.velocity.properties";
130:
131: /**
132: * Use of this properties key has been deprecated, and will be
133: * removed in Velocity version 1.5.
134: */
135: private static final String OLD_INIT_PROPS_KEY = "properties";
136:
137: /**
138: * Cache of writers
139: */
140:
141: private static SimplePool writerPool = new SimplePool(40);
142:
143: /**
144: * Performs initialization of this servlet. Called by the servlet
145: * container on loading.
146: *
147: * @param config The servlet configuration to apply.
148: *
149: * @exception ServletException
150: */
151: public void init(ServletConfig config) throws ServletException {
152: super .init(config);
153:
154: /*
155: * do whatever we have to do to init Velocity
156: */
157: initVelocity(config);
158:
159: /*
160: * Now that Velocity is initialized, cache some config.
161: */
162: VelocityServlet.defaultContentType = RuntimeSingleton
163: .getString(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
164: }
165:
166: /**
167: * Initializes the Velocity runtime, first calling
168: * loadConfiguration(ServletConvig) to get a
169: * java.util.Properties of configuration information
170: * and then calling Velocity.init(). Override this
171: * to do anything to the environment before the
172: * initialization of the singelton takes place, or to
173: * initialize the singleton in other ways.
174: * @param config
175: * @throws ServletException
176: */
177: protected void initVelocity(ServletConfig config)
178: throws ServletException {
179: try {
180: /*
181: * call the overridable method to allow the
182: * derived classes a shot at altering the configuration
183: * before initializing Runtime
184: */
185:
186: Properties props = loadConfiguration(config);
187:
188: Velocity.init(props);
189: } catch (Exception e) {
190: throw new ServletException("Error initializing Velocity: "
191: + e, e);
192: }
193: }
194:
195: /**
196: * Loads the configuration information and returns that
197: * information as a Properties, which will be used to
198: * initialize the Velocity runtime.
199: * <br><br>
200: * Currently, this method gets the initialization parameter
201: * VelocityServlet.INIT_PROPS_KEY, which should be a file containing
202: * the configuration information.
203: * <br><br>
204: * To configure your Servlet Spec 2.2 compliant servlet runner to pass
205: * this to you, put the following in your WEB-INF/web.xml file
206: * <br>
207: * <pre>
208: * <servlet>
209: * <servlet-name> YourServlet </servlet-name>
210: * <servlet-class> your.package.YourServlet </servlet-class>
211: * <init-param>
212: * <param-name> org.apache.velocity.properties </param-name>
213: * <param-value> velocity.properties </param-value>
214: * </init-param>
215: * </servlet>
216: * </pre>
217: *
218: * Alternately, if you wish to configure an entire context in this
219: * fashion, you may use the following:
220: * <br>
221: * <pre>
222: * <context-param>
223: * <param-name> org.apache.velocity.properties </param-name>
224: * <param-value> velocity.properties </param-value>
225: * <description> Path to Velocity configuration </description>
226: * </context-param>
227: * </pre>
228: *
229: * Derived classes may do the same, or take advantage of this code to do the loading for them via :
230: * <pre>
231: * Properties p = super.loadConfiguration( config );
232: * </pre>
233: * and then add or modify the configuration values from the file.
234: * <br>
235: *
236: * @param config ServletConfig passed to the servlets init() function
237: * Can be used to access the real path via ServletContext (hint)
238: * @return java.util.Properties loaded with configuration values to be used
239: * to initialize the Velocity runtime.
240: * @throws FileNotFoundException if a specified file is not found.
241: * @throws IOException I/O problem accessing the specified file, if specified.
242: * @deprecated Use VelocityViewServlet from the Velocity Tools
243: * library instead.
244: */
245: protected Properties loadConfiguration(ServletConfig config)
246: throws IOException, FileNotFoundException {
247: // This is a little overly complex because of legacy support
248: // for the initialization properties key "properties".
249: // References to OLD_INIT_PROPS_KEY should be removed at
250: // Velocity version 1.5.
251: String propsFile = config.getInitParameter(INIT_PROPS_KEY);
252: if (propsFile == null || propsFile.length() == 0) {
253: ServletContext sc = config.getServletContext();
254: propsFile = config.getInitParameter(OLD_INIT_PROPS_KEY);
255: if (propsFile == null || propsFile.length() == 0) {
256: propsFile = sc.getInitParameter(INIT_PROPS_KEY);
257: if (propsFile == null || propsFile.length() == 0) {
258: propsFile = sc.getInitParameter(OLD_INIT_PROPS_KEY);
259: if (propsFile != null && propsFile.length() > 0) {
260: sc.log("Use of the properties initialization "
261: + "parameter '" + OLD_INIT_PROPS_KEY
262: + "' has " + "been deprecated by '"
263: + INIT_PROPS_KEY + '\'');
264: }
265: }
266: } else {
267: sc
268: .log("Use of the properties initialization parameter '"
269: + OLD_INIT_PROPS_KEY
270: + "' has been deprecated by '"
271: + INIT_PROPS_KEY + '\'');
272: }
273: }
274:
275: /*
276: * This will attempt to find the location of the properties
277: * file from the relative path to the WAR archive (ie:
278: * docroot). Since JServ returns null for getRealPath()
279: * because it was never implemented correctly, then we know we
280: * will not have an issue with using it this way. I don't know
281: * if this will break other servlet engines, but it probably
282: * shouldn't since WAR files are the future anyways.
283: */
284:
285: Properties p = new Properties();
286:
287: if (propsFile != null) {
288: p.load(getServletContext().getResourceAsStream(propsFile));
289: }
290:
291: return p;
292: }
293:
294: /**
295: * Handles HTTP <code>GET</code> requests by calling {@link
296: * #doRequest(HttpServletRequest, HttpServletResponse)}.
297: * @param request
298: * @param response
299: * @throws ServletException
300: * @throws IOException
301: */
302: public void doGet(HttpServletRequest request,
303: HttpServletResponse response) throws ServletException,
304: IOException {
305: doRequest(request, response);
306: }
307:
308: /**
309: * Handles HTTP <code>POST</code> requests by calling {@link
310: * #doRequest(HttpServletRequest, HttpServletResponse)}.
311: * @param request
312: * @param response
313: * @throws ServletException
314: * @throws IOException
315: */
316: public void doPost(HttpServletRequest request,
317: HttpServletResponse response) throws ServletException,
318: IOException {
319: doRequest(request, response);
320: }
321:
322: /**
323: * Handles all requests (by default).
324: *
325: * @param request HttpServletRequest object containing client request
326: * @param response HttpServletResponse object for the response
327: * @throws ServletException
328: * @throws IOException
329: */
330: protected void doRequest(HttpServletRequest request,
331: HttpServletResponse response) throws ServletException,
332: IOException {
333: Context context = null;
334: try {
335: /*
336: * first, get a context
337: */
338:
339: context = createContext(request, response);
340:
341: /*
342: * set the content type
343: */
344:
345: setContentType(request, response);
346:
347: /*
348: * let someone handle the request
349: */
350:
351: Template template = handleRequest(request, response,
352: context);
353: /*
354: * bail if we can't find the template
355: */
356:
357: if (template == null) {
358: return;
359: }
360:
361: /*
362: * now merge it
363: */
364:
365: mergeTemplate(template, context, response);
366: } catch (Exception e) {
367: /*
368: * call the error handler to let the derived class
369: * do something useful with this failure.
370: */
371:
372: error(request, response, e);
373: } finally {
374: /*
375: * call cleanup routine to let a derived class do some cleanup
376: */
377:
378: requestCleanup(request, response, context);
379: }
380: }
381:
382: /**
383: * A cleanup routine which is called at the end of the {@link
384: * #doRequest(HttpServletRequest, HttpServletResponse)}
385: * processing sequence, allowing a derived class to do resource
386: * cleanup or other end of process cycle tasks.
387: *
388: * @param request servlet request from client
389: * @param response servlet reponse
390: * @param context context created by the createContext() method
391: */
392: protected void requestCleanup(HttpServletRequest request,
393: HttpServletResponse response, Context context) {
394: }
395:
396: /**
397: * merges the template with the context. Only override this if you really, really
398: * really need to. (And don't call us with questions if it breaks :)
399: *
400: * @param template template object returned by the handleRequest() method
401: * @param context context created by the createContext() method
402: * @param response servlet reponse (use this to get the output stream or Writer
403: * @throws ResourceNotFoundException
404: * @throws ParseErrorException
405: * @throws MethodInvocationException
406: * @throws IOException
407: * @throws UnsupportedEncodingException
408: * @throws Exception
409: */
410: protected void mergeTemplate(Template template, Context context,
411: HttpServletResponse response)
412: throws ResourceNotFoundException, ParseErrorException,
413: MethodInvocationException, IOException,
414: UnsupportedEncodingException, Exception {
415: ServletOutputStream output = response.getOutputStream();
416: VelocityWriter vw = null;
417: // ASSUMPTION: response.setContentType() has been called.
418: String encoding = response.getCharacterEncoding();
419:
420: try {
421: vw = (VelocityWriter) writerPool.get();
422:
423: if (vw == null) {
424: vw = new VelocityWriter(new OutputStreamWriter(output,
425: encoding), 4 * 1024, true);
426: } else {
427: vw.recycle(new OutputStreamWriter(output, encoding));
428: }
429:
430: template.merge(context, vw);
431: } finally {
432: if (vw != null) {
433: try {
434: /*
435: * flush and put back into the pool
436: * don't close to allow us to play
437: * nicely with others.
438: */
439: vw.flush();
440: } catch (IOException e) {
441: // do nothing
442: }
443:
444: /*
445: * Clear the VelocityWriter's reference to its
446: * internal OutputStreamWriter to allow the latter
447: * to be GC'd while vw is pooled.
448: */
449: vw.recycle(null);
450: writerPool.put(vw);
451: }
452: }
453: }
454:
455: /**
456: * Sets the content type of the response, defaulting to {@link
457: * #defaultContentType} if not overriden. Delegates to {@link
458: * #chooseCharacterEncoding(HttpServletRequest)} to select the
459: * appropriate character encoding.
460: *
461: * @param request The servlet request from the client.
462: * @param response The servlet reponse to the client.
463: */
464: protected void setContentType(HttpServletRequest request,
465: HttpServletResponse response) {
466: String contentType = VelocityServlet.defaultContentType;
467: int index = contentType.lastIndexOf(';') + 1;
468: if (index <= 0
469: || (index < contentType.length() && contentType
470: .indexOf("charset", index) == -1)) {
471: // Append the character encoding which we'd like to use.
472: String encoding = chooseCharacterEncoding(request);
473: //RuntimeSingleton.debug("Chose output encoding of '" +
474: // encoding + '\'');
475: if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) {
476: contentType += "; charset=" + encoding;
477: }
478: }
479: response.setContentType(contentType);
480: //RuntimeSingleton.debug("Response Content-Type set to '" +
481: // contentType + '\'');
482: }
483:
484: /**
485: * Chooses the output character encoding to be used as the value
486: * for the "charset=" portion of the HTTP Content-Type header (and
487: * thus returned by <code>response.getCharacterEncoding()</code>).
488: * Called by {@link #setContentType(HttpServletRequest,
489: * HttpServletResponse)} if an encoding isn't already specified by
490: * Content-Type. By default, chooses the value of
491: * RuntimeSingleton's <code>output.encoding</code> property.
492: *
493: * @param request The servlet request from the client.
494: * @return The chosen character encoding.
495: */
496: protected String chooseCharacterEncoding(HttpServletRequest request) {
497: return RuntimeSingleton.getString(
498: RuntimeConstants.OUTPUT_ENCODING,
499: DEFAULT_OUTPUT_ENCODING);
500: }
501:
502: /**
503: * Returns a context suitable to pass to the handleRequest() method
504: * <br><br>
505: * Default implementation will create a VelocityContext object,
506: * put the HttpServletRequest and HttpServletResponse
507: * into the context accessable via the keys VelocityServlet.REQUEST and
508: * VelocityServlet.RESPONSE, respectively.
509: *
510: * @param request servlet request from client
511: * @param response servlet reponse to client
512: *
513: * @return context
514: */
515: protected Context createContext(HttpServletRequest request,
516: HttpServletResponse response) {
517: /*
518: * create a new context
519: */
520:
521: VelocityContext context = new VelocityContext();
522:
523: /*
524: * put the request/response objects into the context
525: * wrap the HttpServletRequest to solve the introspection
526: * problems
527: */
528:
529: context.put(REQUEST, request);
530: context.put(RESPONSE, response);
531:
532: return context;
533: }
534:
535: /**
536: * Retrieves the requested template.
537: *
538: * @param name The file name of the template to retrieve relative to the
539: * template root.
540: * @return The requested template.
541: * @throws ResourceNotFoundException if template not found
542: * from any available source.
543: * @throws ParseErrorException if template cannot be parsed due
544: * to syntax (or other) error.
545: * @throws Exception if an error occurs in template initialization
546: */
547: public Template getTemplate(String name)
548: throws ResourceNotFoundException, ParseErrorException,
549: Exception {
550: return RuntimeSingleton.getTemplate(name);
551: }
552:
553: /**
554: * Retrieves the requested template with the specified
555: * character encoding.
556: *
557: * @param name The file name of the template to retrieve relative to the
558: * template root.
559: * @param encoding the character encoding of the template
560: *
561: * @return The requested template.
562: * @throws ResourceNotFoundException if template not found
563: * from any available source.
564: * @throws ParseErrorException if template cannot be parsed due
565: * to syntax (or other) error.
566: * @throws Exception if an error occurs in template initialization
567: *
568: * @since Velocity v1.1
569: */
570: public Template getTemplate(String name, String encoding)
571: throws ResourceNotFoundException, ParseErrorException,
572: Exception {
573: return RuntimeSingleton.getTemplate(name, encoding);
574: }
575:
576: /**
577: * Implement this method to add your application data to the context,
578: * calling the <code>getTemplate()</code> method to produce your return
579: * value.
580: * <br><br>
581: * In the event of a problem, you may handle the request directly
582: * and return <code>null</code> or throw a more meaningful exception
583: * for the error handler to catch.
584: *
585: * @param request servlet request from client
586: * @param response servlet reponse
587: * @param ctx The context to add your data to.
588: * @return The template to merge with your context or null, indicating
589: * that you handled the processing.
590: * @throws Exception
591: *
592: * @since Velocity v1.1
593: */
594: protected Template handleRequest(HttpServletRequest request,
595: HttpServletResponse response, Context ctx) throws Exception {
596: /*
597: * invoke handleRequest
598: */
599:
600: Template t = handleRequest(ctx);
601:
602: /*
603: * if it returns null, this is the 'old' deprecated
604: * way, and we want to mimic the behavior for a little
605: * while anyway
606: */
607:
608: if (t == null) {
609: throw new Exception(
610: "handleRequest(Context) returned null - no template selected!");
611: }
612:
613: return t;
614: }
615:
616: /**
617: * Implement this method to add your application data to the context,
618: * calling the <code>getTemplate()</code> method to produce your return
619: * value.
620: * <br><br>
621: * In the event of a problem, you may simple return <code>null</code>
622: * or throw a more meaningful exception.
623: *
624: * @deprecated Use
625: * {@link #handleRequest( HttpServletRequest request,
626: * HttpServletResponse response, Context ctx )}
627: *
628: * @param ctx The context to add your data to.
629: * @return The template to merge with your context.
630: * @throws Exception
631: */
632: protected Template handleRequest(Context ctx) throws Exception {
633: throw new Exception(
634: "You must override VelocityServlet.handleRequest( Context) "
635: + " or VelocityServlet.handleRequest( HttpServletRequest, "
636: + " HttpServletResponse, Context)");
637: }
638:
639: /**
640: * Invoked when there is an error thrown in any part of doRequest() processing.
641: * <br><br>
642: * Default will send a simple HTML response indicating there was a problem.
643: *
644: * @param request original HttpServletRequest from servlet container.
645: * @param response HttpServletResponse object from servlet container.
646: * @param cause Exception that was thrown by some other part of process.
647: * @throws ServletException
648: * @throws IOException
649: */
650: protected void error(HttpServletRequest request,
651: HttpServletResponse response, Exception cause)
652: throws ServletException, IOException {
653: StringBuffer html = new StringBuffer();
654: html.append("<html>");
655: html.append("<title>Error</title>");
656: html.append("<body bgcolor=\"#ffffff\">");
657: html
658: .append("<h2>VelocityServlet: Error processing the template</h2>");
659: html.append("<pre>");
660: String why = cause.getMessage();
661: if (why != null && why.trim().length() > 0) {
662: html.append(why);
663: html.append("<br>");
664: }
665:
666: StringWriter sw = new StringWriter();
667: cause.printStackTrace(new PrintWriter(sw));
668:
669: html.append(sw.toString());
670: html.append("</pre>");
671: html.append("</body>");
672: html.append("</html>");
673: response.getOutputStream().print(html.toString());
674: }
675: }
|