View Javadoc

1   package org.apache.velocity.tools.view;
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.InputStream;
23  import java.io.IOException;
24  import java.io.Writer;
25  import java.util.List;
26  import javax.servlet.FilterConfig;
27  import javax.servlet.ServletConfig;
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletRequest;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.servlet.http.HttpSession;
33  import org.apache.commons.collections.ExtendedProperties;
34  import org.apache.velocity.Template;
35  import org.apache.velocity.app.VelocityEngine;
36  import org.apache.velocity.context.Context;
37  import org.apache.velocity.exception.ResourceNotFoundException;
38  import org.apache.velocity.io.VelocityWriter;
39  import org.apache.velocity.runtime.RuntimeConstants;
40  import org.apache.velocity.runtime.log.Log;
41  import org.apache.velocity.tools.generic.log.LogChuteCommonsLog;
42  import org.apache.velocity.tools.ClassUtils;
43  import org.apache.velocity.tools.Scope;
44  import org.apache.velocity.tools.Toolbox;
45  import org.apache.velocity.tools.ToolboxFactory;
46  import org.apache.velocity.tools.config.ConfigurationCleaner;
47  import org.apache.velocity.tools.config.ConfigurationUtils;
48  import org.apache.velocity.tools.config.FactoryConfiguration;
49  import org.apache.velocity.tools.view.ViewToolContext;
50  import org.apache.velocity.tools.view.context.ChainedContext;
51  import org.apache.velocity.util.SimplePool;
52  
53  /**
54   * <p>The class provides the following features:</p>
55   * <ul>
56   *   <li>renders Velocity templates</li>
57   *   <li>provides support for an auto-loaded, configurable toolbox</li>
58   *   <li>provides transparent access to the servlet request attributes,
59   *       servlet session attributes and servlet context attributes by
60   *       auto-searching them</li>
61   *   <li>logs to the logging facility of the servlet API</li>
62   * </ul>
63   *
64   * <p>VelocityView supports the following configuration parameters
65   * in web.xml:</p>
66   * <dl>
67   *   <dt>org.apache.velocity.tools</dt>
68   *   <dd>Path and name of the toolbox configuration file. The path must be
69   *     relative to the web application root directory. If this parameter is
70   *     not found, the servlet will check for a toolbox file at
71   *     '/WEB-INF/tools.xml'.</dd>
72   *   <dt>org.apache.velocity.properties</dt>
73   *   <dd>Path and name of the Velocity configuration file. The path must be
74   *     relative to the web application root directory. If this parameter
75   *     is not present, Velocity will check for a properties file at
76   *     '/WEB-INF/velocity.properties'.  If no file is found there, then
77   *     Velocity is initialized with the settings in the classpath at
78   *     'org.apache.velocity.tools.view.velocity.properties'.</dd>
79   * </dl>
80   *
81   * @author Dave Bryson
82   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
83   * @author <a href="mailto:sidler@teamup.com">Gabe Sidler</a>
84   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
85   * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
86   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
87   * @author Nathan Bubna
88   *
89   * @version $Id: VelocityView.java 511959 2007-02-26 19:24:39Z nbubna $
90   */
91  public class VelocityView extends ViewToolManager
92  {
93      /** The HTTP content type context key. */
94      public static final String CONTENT_TYPE_KEY = "default.contentType";
95  
96      /**
97       * Key used to access the ServletContext in
98       * the Velocity application attributes.
99       */
100     public static final String SERVLET_CONTEXT_KEY =
101         ServletContext.class.getName();
102 
103     /** The default content type for the response */
104     public static final String DEFAULT_CONTENT_TYPE = "text/html";
105 
106     /** Default encoding for the output stream */
107     public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1";
108 
109     /**
110      * Key used to access the toolbox configuration file path from the
111      * Servlet or webapp init parameters ("org.apache.velocity.tools")
112      * or to access a live {@link FactoryConfiguration} previously
113      * placed in the ServletContext attributes.
114      */
115     public static final String TOOLS_KEY = ServletUtils.CONFIGURATION_KEY;
116     @Deprecated
117     public static final String DEPRECATED_TOOLS_KEY =
118         "org.apache.velocity.toolbox";
119 
120     /**
121      * Default toolbox configuration file path. If no alternate value for
122      * this is specified, the servlet will look here.
123      */
124     public static final String USER_TOOLS_PATH =
125         "/WEB-INF/tools.xml";
126     @Deprecated
127     public static final String DEPRECATED_USER_TOOLS_PATH =
128         "/WEB-INF/toolbox.xml";
129 
130     /**
131      * Default Runtime properties.
132      */
133     public static final String DEFAULT_PROPERTIES_PATH =
134         "/org/apache/velocity/tools/view/velocity.properties";
135 
136     /**
137      * This is the string that is looked for when getInitParameter is
138      * called ("org.apache.velocity.properties").
139      */
140     public static final String PROPERTIES_KEY =
141         "org.apache.velocity.properties";
142 
143     /**
144      * Default velocity properties file path. If no alternate value for
145      * this is specified, the servlet will look here.
146      */
147     public  static final String USER_PROPERTIES_PATH =
148         "/WEB-INF/velocity.properties";
149 
150     /**
151      * Controls loading of available default tool configurations
152      * provided by VelocityTools.  The default behavior is conditional;
153      * if {@link #DEPRECATION_SUPPORT_MODE_KEY} has not been set to
154      * {@code false} and there is an old {@code toolbox.xml} configuration
155      * present, then the defaults will not be loaded unless you explicitly
156      * set this property to {@code true} in your init params.  If there
157      * is no {@code toolbox.xml} and/or the deprecation support is turned off,
158      * then the default tools will be loaded automatically unless you
159      * explicitly set this property to {@code false} in your init params.
160      */
161     public static final String LOAD_DEFAULTS_KEY =
162         "org.apache.velocity.tools.loadDefaults";
163 
164     /**
165      * Controls removal of tools or data with invalid configurations
166      * before initialization is finished.
167      * The default is false; set to {@code true} to turn this feature on.
168      */
169     public static final String CLEAN_CONFIGURATION_KEY =
170         "org.apache.velocity.tools.cleanConfiguration";
171 
172     /**
173      * Controls whether or not templates can overwrite tool and servlet API
174      * variables in the local context. The default is true; set to {@code false}
175      * to prevent overwriting of any tool variables.
176      */
177     public static final String USER_OVERWRITE_KEY =
178         "org.apache.velocity.tools.userCanOverwriteTools";
179 
180     /**
181      * Controls support for deprecated tools and configuration.
182      * The default is {@code true}; set to {@code false} to turn off
183      * support for deprecated tools and configuration.
184      */
185     public static final String DEPRECATION_SUPPORT_MODE_KEY =
186         "org.apache.velocity.tools.deprecationSupportMode";
187 
188 
189     private static SimplePool writerPool = new SimplePool(40);
190     private String defaultContentType = DEFAULT_CONTENT_TYPE;
191     private boolean deprecationSupportMode = true;
192 
193     public VelocityView(ServletConfig config)
194     {
195         this(new JeeServletConfig(config));
196     }
197 
198     public VelocityView(FilterConfig config)
199     {
200         this(new JeeFilterConfig(config));
201     }
202 
203     public VelocityView(ServletContext context)
204     {
205         this(new JeeContextConfig(context));
206     }
207 
208     public VelocityView(JeeConfig config)
209     {
210         // suppress auto-config, as we have our own config lookup order here
211         super(config.getServletContext(), false, false);
212 
213         init(config);
214     }
215 
216     @Deprecated
217     protected final void setDeprecationSupportMode(boolean support)
218     {
219         if (deprecationSupportMode != support)
220         {
221             this.deprecationSupportMode = support;
222             debug("deprecationSupportMode is now %s", (support ? "on" : "off"));
223         }
224     }
225 
226     /**
227      * Overrides super class to ensure engine is not set to null.
228      */
229     @Override
230     public void setVelocityEngine(VelocityEngine engine)
231     {
232         if (engine == null)
233         {
234             throw new NullPointerException("VelocityEngine cannot be null");
235         }
236         super.setVelocityEngine(engine);
237     }
238 
239     /**
240      * Returns the configured default Content-Type.
241      */
242     public String getDefaultContentType()
243     {
244         return this.defaultContentType;
245     }
246 
247     /**
248      * Sets the configured default Content-Type.
249      */
250     public void setDefaultContentType(String type)
251     {
252         if (!defaultContentType.equals(type))
253         {
254             this.defaultContentType = type;
255             debug("Default Content-Type was changed to %s", type);
256         }
257     }
258 
259     /**
260      * Simplifies process of getting a property from VelocityEngine,
261      * because the VelocityEngine interface sucks compared to the singleton's.
262      * Use of this method assumes that {@link #init(JeeConfig,VelocityEngine)}
263      * has already been called.
264      */
265     protected String getProperty(String key, String alternate)
266     {
267         String prop = (String)velocity.getProperty(key);
268         if (prop == null || prop.length() == 0)
269         {
270             return alternate;
271         }
272         return prop;
273     }
274 
275 
276     /**
277      * <p>Initializes ToolboxFactory, VelocityEngine, and sets default
278      * encoding for processing requests.</p>
279      *
280      * <p>NOTE: If no charset is specified in the default.contentType
281      * property (in your velocity.properties) and you have specified
282      * an output.encoding property, then that will be used as the
283      * charset for the default content-type of pages served by this
284      * servlet.</p>
285      *
286      * @param config servlet configuation
287      */
288     protected void init(JeeConfig config)
289     {
290         // create an engine if none is set yet
291         // (servletContext and factory should already be set by now
292         if (this.velocity == null)
293         {
294             this.velocity = new VelocityEngine();
295         }
296 
297         // default is true for these, so just watch for false
298         String depMode = config.findInitParameter(DEPRECATION_SUPPORT_MODE_KEY);
299         if (depMode != null && depMode.equalsIgnoreCase("false"))
300         {
301             setDeprecationSupportMode(false);
302         }
303         String allowOverwrite = config.findInitParameter(USER_OVERWRITE_KEY);
304         if (allowOverwrite != null && allowOverwrite.equalsIgnoreCase("false"))
305         {
306             setUserCanOverwriteTools(false);
307         }
308 
309         // configure and initialize the VelocityEngine
310         init(config, velocity);
311 
312         // configure the ToolboxFactory
313         configure(config, factory);
314 
315         // set encoding & content-type
316         setEncoding(config);
317     }
318 
319     /**
320      * Initializes the Velocity runtime, first calling
321      * loadConfiguration(JeeConfig) to get a
322      * org.apache.commons.collections.ExtendedProperties
323      * of configuration information
324      * and then calling velocityEngine.init().  Override this
325      * to do anything to the environment before the
326      * initialization of the singleton takes place, or to
327      * initialize the singleton in other ways.
328      *
329      * @param config servlet configuration parameters
330      */
331     protected void init(JeeConfig config, final VelocityEngine velocity)
332     {
333         // register this engine to be the default handler of log messages
334         // if the user points commons-logging to the LogSystemCommonsLog
335         LogChuteCommonsLog.setVelocityLog(getLog());
336 
337         // put the servlet context into Velocity's application attributes,
338         // where the WebappResourceLoader can find them
339         velocity.setApplicationAttribute(SERVLET_CONTEXT_KEY, this.servletContext);
340 
341         // configure the engine itself
342         configure(config, velocity);
343 
344         // now all is ready - init Velocity
345         try
346         {
347             velocity.init();
348         }
349         catch(Exception e)
350         {
351             String msg = "Could not initialize VelocityEngine";
352             getLog().error(msg, e);
353             e.printStackTrace();
354             throw new RuntimeException(msg + ": "+e, e);
355         }
356     }
357 
358     protected void configure(final JeeConfig config, final VelocityEngine velocity)
359     {
360         // first get the default properties, and bail if we don't find them
361 	ExtendedProperties defaultProperties = getProperties(DEFAULT_PROPERTIES_PATH, true);
362 	// if using Velocity engine prior to 1.6.x, remove WebappUberspector
363 	// (this hack will disappear once tools require Velocity 1.6.x+)
364 	try {
365 	    Class.forName("org.apache.velocity.tools.view.WebappUberspector");
366 	} catch(Throwable t) {
367 	    // remove WebappUberspector from the list of introspectors
368 	    List introspectors = defaultProperties.getList(VelocityEngine.UBERSPECT_CLASSNAME);
369 	    introspectors.remove("org.apache.velocity.tools.view.WebappUberspector");
370 	    defaultProperties.setProperty(VelocityEngine.UBERSPECT_CLASSNAME,introspectors);
371 	}
372         velocity.setExtendedProperties(defaultProperties);
373 
374         // check for application-wide user props in the context init params
375         String appPropsPath = servletContext.getInitParameter(PROPERTIES_KEY);
376         setProps(velocity, appPropsPath, true);
377 
378         // check for servlet-wide user props in the config init params at the
379         // conventional location, and be silent if they're missing
380         setProps(velocity, USER_PROPERTIES_PATH, false);
381 
382         // check for a custom location for servlet-wide user props
383         String servletPropsPath = config.getInitParameter(PROPERTIES_KEY);
384         setProps(velocity, servletPropsPath, true);
385     }
386 
387     private boolean setProps(VelocityEngine velocity, String path, boolean require)
388     {
389         if (path == null)
390         {
391             // only bother with this if a path was given
392             return false;
393         }
394 
395         // this will throw an exception if require is true and there
396         // are no properties at the path.  if require is false, this
397         // will return null when there's no properties at the path
398         ExtendedProperties props = getProperties(path, require);
399         if (props == null)
400         {
401             return false;
402         }
403 
404         debug("Configuring Velocity with properties at: %s", path);
405 
406         // these props will override those already set
407         velocity.setExtendedProperties(props);
408         // notify that new props were set
409         return true;
410     }
411 
412 
413     /**
414      * Here's the configuration lookup/loading order:
415      * <ol>
416      * <li>If deprecationSupportMode is true:
417      *   <ol>
418      *   <li>Config file optionally specified by {@code org.apache.velocity.toolbox} init-param (servlet or servletContext)</li>
419      *   <li>If none, config file optionally at {@code /WEB-INF/toolbox.xml} (deprecated conventional location)</li>
420      *   </ol>
421      * </li>
422      * <li>If no old toolbox or loadDefaults is true, {@link ConfigurationUtils#getDefaultTools()}</li>
423      * <li>{@link ConfigurationUtils#getAutoLoaded}(false)</li>
424      * <li>Config file optionally specified by servletContext {@code org.apache.velocity.tools} init-param</li>
425      * <li>Config file optionally at {@code /WEB-INF/tools.xml} (new conventional location)</li>
426      * <li>Config file optionally specified by servlet {@code org.apache.velocity.tools} init-param</li>
427      * </ol>
428      * Remember that as these configurations are added on top of each other,
429      * the newer values will always override the older ones.  Also, once they
430      * are all loaded, this method can "clean" your configuration of all invalid
431      * tool, toolbox or data configurations if you set the 
432      * {@code org.apache.velocity.tools.cleanConfiguration} init-param to true in
433      * either your servlet or servletContext init-params.
434      */
435     protected void configure(final JeeConfig config, final ToolboxFactory factory)
436     {
437         FactoryConfiguration factoryConfig = new FactoryConfiguration("VelocityView.configure(config,factory)");
438 
439         boolean hasOldToolbox = false;
440         if (this.deprecationSupportMode)
441         {
442             FactoryConfiguration oldToolbox = getDeprecatedConfig(config);
443             if (oldToolbox != null)
444             {
445                 hasOldToolbox = true;
446                 factoryConfig.addConfiguration(oldToolbox);
447             }
448         }
449 
450         // only load the default tools if they have explicitly said to
451         // or if they are not using an old toolbox and have said nothing
452         String loadDefaults = config.findInitParameter(LOAD_DEFAULTS_KEY);
453         if ((!hasOldToolbox && loadDefaults == null) ||
454             "true".equalsIgnoreCase(loadDefaults))
455         {
456             // add all available default tools
457             getLog().trace("Loading default tools configuration...");
458             factoryConfig.addConfiguration(ConfigurationUtils.getDefaultTools());
459         }
460         else
461         {
462             // let the user know that the defaults were suppressed
463             debug("Default tools configuration has been suppressed%s",
464                   (hasOldToolbox ?
465                    " to avoid conflicts with older application's context and toolbox definition." :
466                    "."));
467         }
468 
469         // this gets the auto loaded config from the classpath
470         // this doesn't include defaults since they're handled already
471         // and it could theoretically pick up an auto-loaded config from the
472         // filesystem, but that is highly unlikely to happen in a webapp env
473         FactoryConfiguration autoLoaded = ConfigurationUtils.getAutoLoaded(false);
474         factoryConfig.addConfiguration(autoLoaded);
475 
476         // check for application-wide user config in the context init params
477         String appToolsPath = servletContext.getInitParameter(TOOLS_KEY);
478         setConfig(factoryConfig, appToolsPath, true);
479 
480         // check for user configuration at the conventional location,
481         // and be silent if they're missing
482         setConfig(factoryConfig, USER_TOOLS_PATH, false);
483 
484         // check for a custom location for servlet-wide user props
485         String servletToolsPath = config.getInitParameter(TOOLS_KEY);
486         setConfig(factoryConfig, servletToolsPath, true);
487 
488         // check for "injected" configuration in application attributes
489         FactoryConfiguration injected = ServletUtils.getConfiguration(servletContext);
490         if (injected != null)
491         {
492             debug("Adding configuration instance in servletContext attributes as '%s'", TOOLS_KEY);
493             factoryConfig.addConfiguration(injected);
494         }
495 
496         // see if we should only keep valid tools, data, and properties
497         String cleanConfig = config.findInitParameter(CLEAN_CONFIGURATION_KEY);
498         if ("true".equals(cleanConfig))
499         {
500             // remove invalid tools, data, and properties from the configuration
501             ConfigurationCleaner cleaner = new ConfigurationCleaner();
502             cleaner.setLog(getLog());
503             cleaner.clean(factoryConfig);
504         }
505 
506         // apply this configuration to the specified factory
507         debug("Configuring factory with: %s", factoryConfig);
508         configure(factoryConfig);
509     }
510 
511     /**
512      * First tries to find a path to a toolbox under the deprecated
513      * {@code org.apache.velocity.toolbox} key.
514      * If found, it tries to load the configuration there and will blow up
515      * if there is no config file there.
516      * If not found, it looks for a config file at /WEB-INF/toolbox.xml
517      * (the deprecated default location) and tries to load it if found.
518      */
519     @Deprecated
520     protected FactoryConfiguration getDeprecatedConfig(JeeConfig config)
521     {
522         FactoryConfiguration toolbox = null;
523 
524         // look for specified path under the deprecated toolbox key
525         String oldPath = config.findInitParameter(DEPRECATED_TOOLS_KEY);
526         if (oldPath != null)
527         {
528             // ok, they said the toolbox.xml should be there
529             // so this should blow up if it is not
530             toolbox = getConfiguration(oldPath, true);
531         }
532         else
533         {
534             // check for deprecated user configuration at the old conventional
535             // location.  be silent if missing, log deprecation warning otherwise
536             oldPath = DEPRECATED_USER_TOOLS_PATH;
537             toolbox = getConfiguration(oldPath);
538         }
539 
540         if (toolbox != null)
541         {
542             debug("Loaded deprecated configuration from: %s", oldPath);
543             getLog().warn("Please upgrade to new \"/WEB-INF/tools.xml\" format and conventional location."+
544                           " Support for \"/WEB-INF/toolbox.xml\" format and conventional file name will "+
545                           "be removed in a future version.");
546         }
547         return toolbox;
548     }
549 
550     private boolean setConfig(FactoryConfiguration factory, String path, boolean require)
551     {
552         if (path == null)
553         {
554             // only bother with this if a path was given
555             return false;
556         }
557 
558         // this will throw an exception if require is true and there
559         // is no tool config at the path.  if require is false, this
560         // will return null when there's no tool config at the path
561         FactoryConfiguration config = getConfiguration(path, require);
562         if (config == null)
563         {
564             return false;
565         }
566 
567         debug("Loaded configuration from: %s", path);
568         factory.addConfiguration(config);
569 
570         // notify that new config was added
571         return true;
572     }
573 
574 
575     protected InputStream getInputStream(String path, boolean required)
576     {
577         // first, search the classpath
578         InputStream inputStream = ServletUtils.getInputStream(path, this.servletContext);
579 
580         // if we didn't find one
581         if (inputStream == null)
582         {
583             String msg = "Did not find resource at: "+path;
584             if (required)
585             {
586                 getLog().error(msg);
587                 throw new ResourceNotFoundException(msg);
588             }
589             debug(msg);
590             return null;
591         }
592         return inputStream;
593     }
594 
595 
596     protected ExtendedProperties getProperties(String path)
597     {
598         return getProperties(path, false);
599     }
600 
601     protected ExtendedProperties getProperties(String path, boolean required)
602     {
603         if (getLog().isTraceEnabled())
604         {
605             getLog().trace("Searching for properties at: "+path);
606         }
607 
608         InputStream inputStream = getInputStream(path, required);
609         if (inputStream == null)
610         {
611             return null;
612         }
613 
614         ExtendedProperties properties = new ExtendedProperties();
615         try
616         {
617             properties.load(inputStream);
618         }
619         catch (IOException ioe)
620         {
621             String msg = "Failed to load properties at: "+path;
622             getLog().error(msg, ioe);
623             if (required)
624             {
625                 throw new RuntimeException(msg, ioe);
626             }
627         }
628         finally
629         {
630             try
631             {
632                 if (inputStream != null)
633                 {
634                     inputStream.close();
635                 }
636             }
637             catch (IOException ioe)
638             {
639                 getLog().error("Failed to close input stream for "+path, ioe);
640             }
641         }
642         return properties;
643     }
644 
645 
646     protected FactoryConfiguration getConfiguration(String path)
647     {
648         return getConfiguration(path, false);
649     }
650 
651     protected FactoryConfiguration getConfiguration(String path, boolean required)
652     {
653         if (getLog().isTraceEnabled())
654         {
655             getLog().trace("Searching for configuration at: "+path);
656         }
657 
658         FactoryConfiguration config = null;
659         try
660         {
661             config = ServletUtils.getConfiguration(path,
662                                                    this.servletContext,
663                                                    this.deprecationSupportMode);
664             if (config == null)
665             {
666                 String msg = "Did not find resource at: "+path;
667                 if (required)
668                 {
669                     getLog().error(msg);
670                     throw new ResourceNotFoundException(msg);
671                 }
672                 else
673                 {
674                     debug(msg);
675                 }
676             }
677         }
678         catch (ResourceNotFoundException rnfe)
679         {
680             // no need to re-log this
681             throw rnfe;
682         }
683         catch (RuntimeException re)
684         {
685             if (required)
686             {
687                 getLog().error(re.getMessage(), re);
688                 throw re;
689             }
690             getLog().debug(re.getMessage(), re);
691         }
692         return config;
693     }
694 
695 
696     protected void setEncoding(JeeConfig config)
697     {
698         // we can get these now that velocity is initialized
699         this.defaultContentType =
700             getProperty(CONTENT_TYPE_KEY, DEFAULT_CONTENT_TYPE);
701 
702         String encoding = getProperty(RuntimeConstants.OUTPUT_ENCODING,
703                                       DEFAULT_OUTPUT_ENCODING);
704 
705         // For non Latin-1 encodings, ensure that the charset is
706         // included in the Content-Type header.
707         if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding))
708         {
709             int index = defaultContentType.lastIndexOf("charset");
710             if (index < 0)
711             {
712                 // the charset specifier is not yet present in header.
713                 // append character encoding to default content-type
714                 this.defaultContentType += "; charset=" + encoding;
715             }
716             else
717             {
718                 // The user may have configuration issues.
719                 getLog().info("Charset was already " +
720                               "specified in the Content-Type property.  " +
721                               "Output encoding property will be ignored.");
722             }
723         }
724 
725         debug("Default Content-Type is: %s", defaultContentType);
726     }
727 
728 
729 
730 
731 
732     /******************* REQUEST PROCESSING ****************************/
733 
734     /**
735      * 
736      *
737      * @param request  HttpServletRequest object containing client request
738      * @param response HttpServletResponse object for the response
739      * @return the {@link Context} prepared and used to perform the rendering
740      *         to allow proper cleanup afterward
741      */
742     public Context render(HttpServletRequest request,
743                           HttpServletResponse response) throws IOException
744     {
745         // then get a context
746         Context context = createContext(request, response);
747 
748         // get the template
749         Template template = getTemplate(request, response);
750 
751         // merge the template and context into the response
752         merge(template, context, response.getWriter());
753 
754         return context;
755     }
756 
757     public Context render(HttpServletRequest request, Writer out)
758         throws IOException
759     {
760         // then get a context
761         Context context = createContext(request, null);
762 
763         // get the template
764         Template template = getTemplate(request);
765 
766         // merge the template and context into the writer
767         merge(template, context, out);
768 
769         return context;
770     }
771 
772 
773     /**
774      * <p>Creates and returns an initialized Velocity context.</p>
775      *
776      * A new context of class {@link ViewToolContext} is created and
777      * initialized.
778      *
779      * @param request servlet request from client
780      * @param response servlet reponse to client
781      */
782     @Override
783     public ViewToolContext createContext(HttpServletRequest request,
784                                          HttpServletResponse response)
785     {
786         ViewToolContext ctx;
787         if (this.deprecationSupportMode)
788         {
789             ctx = new ChainedContext(velocity, request, response, servletContext);
790         }
791         else
792         {
793             ctx = new ViewToolContext(velocity, request, response, servletContext);
794         }
795         prepareContext(ctx, request);
796         return ctx;
797     }
798 
799 
800     /**
801      * <p>Gets the requested template.</p>
802      *
803      * @param request client request
804      * @return Velocity Template object or null
805      */
806     public Template getTemplate(HttpServletRequest request)
807     {
808         return getTemplate(request, null);
809     }
810 
811     public Template getTemplate(HttpServletRequest request,
812                                    HttpServletResponse response)
813     {
814         String path = ServletUtils.getPath(request);
815         if (response == null)
816         {
817             return getTemplate(path);
818         }
819         else
820         {
821             return getTemplate(path, response.getCharacterEncoding());
822         }
823     }
824 
825 
826     /**
827      * Retrieves the requested template.
828      *
829      * @param name The file name of the template to retrieve relative to the
830      *             template root.
831      * @return The requested template.
832      * @throws ResourceNotFoundException if template not found
833      *          from any available source.
834      * @throws ParseErrorException if template cannot be parsed due
835      *          to syntax (or other) error.
836      */
837     public Template getTemplate(String name)
838     {
839         return getTemplate(name, null);
840     }
841 
842 
843     /**
844      * Retrieves the requested template with the specified character encoding.
845      *
846      * @param name The file name of the template to retrieve relative to the
847      *             template root.
848      * @param encoding the character encoding of the template
849      * @return The requested template.
850      * @throws ResourceNotFoundException if template not found
851      *          from any available source.
852      * @throws ParseErrorException if template cannot be parsed due
853      *          to syntax (or other) error.
854      */
855     public Template getTemplate(String name, String encoding)
856     {
857         try
858         {
859             if (encoding == null)
860             {
861                 return velocity.getTemplate(name);
862             }
863             else
864             {
865                 return velocity.getTemplate(name, encoding);
866             }
867         }
868         catch (RuntimeException e)  // FIXME This is useless with Velocity 1.7
869         {
870             throw e;
871         }
872         catch (Exception e)  // FIXME This is useless with Velocity 1.7
873         {
874             throw new RuntimeException(e);
875         }
876     }
877 
878 
879     /**
880      * Merges the template with the context.  Only override this if you really, really
881      * really need to. (And don't call us with questions if it breaks :)
882      *
883      * @param template template being rendered
884      * @param context Context created by the {@link #createContext}
885      * @param writer into which the content is rendered
886      */
887     public void merge(Template template, Context context, Writer writer)
888         throws IOException
889     {
890         VelocityWriter vw = null;
891         try
892         {
893             vw = (VelocityWriter)writerPool.get();
894             if (vw == null)
895             {
896                 vw = new VelocityWriter(writer, 4 * 1024, true);
897             }
898             else
899             {
900                 vw.recycle(writer);
901             }
902             performMerge(template, context, vw);
903 
904             // flush writer but don't close to allow us to play nicely with others.
905             vw.flush();
906         }
907         finally
908         {
909             if (vw != null)
910             {
911                 try
912                 {
913                     /* This hack sets the VelocityWriter's internal ref to the
914                      * PrintWriter to null to keep memory free while
915                      * the writer is pooled. See bug report #18951 */
916                     vw.recycle(null);
917                     writerPool.put(vw);
918                 }
919                 catch (Exception e)
920                 {
921                     getLog().error("Trouble releasing VelocityWriter: " + 
922                                    e.getMessage(), e);
923                 }
924             }
925         }
926     }
927 
928 
929     /**
930      * This is here so developers may override it and gain access to the
931      * Writer which the template will be merged into.  See
932      * <a href="http://issues.apache.org/jira/browse/VELTOOLS-7">VELTOOLS-7</a>
933      * for discussion of this.
934      *
935      * @param template template object returned by the handleRequest() method
936      * @param context Context created by the {@link #createContext}
937      * @param writer a VelocityWriter that the template is merged into
938      */
939     protected void performMerge(Template template, Context context, Writer writer)
940         throws IOException
941     {
942         template.merge(context, writer);
943     }
944 
945 }