View Javadoc

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