Java tutorial
package org.apache.velocity.tools.view; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.InputStream; import java.io.IOException; import java.io.Writer; import java.util.List; import javax.servlet.FilterConfig; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.collections.ExtendedProperties; import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.io.VelocityWriter; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.log.Log; import org.apache.velocity.tools.generic.log.LogChuteCommonsLog; import org.apache.velocity.tools.ClassUtils; import org.apache.velocity.tools.Scope; import org.apache.velocity.tools.Toolbox; import org.apache.velocity.tools.ToolboxFactory; import org.apache.velocity.tools.config.ConfigurationCleaner; import org.apache.velocity.tools.config.ConfigurationUtils; import org.apache.velocity.tools.config.FactoryConfiguration; import org.apache.velocity.tools.view.ViewToolContext; import org.apache.velocity.tools.view.context.ChainedContext; import org.apache.velocity.util.SimplePool; /** * <p>The class provides the following features:</p> * <ul> * <li>renders Velocity templates</li> * <li>provides support for an auto-loaded, configurable toolbox</li> * <li>provides transparent access to the servlet request attributes, * servlet session attributes and servlet context attributes by * auto-searching them</li> * <li>logs to the logging facility of the servlet API</li> * </ul> * * <p>VelocityView supports the following configuration parameters * in web.xml:</p> * <dl> * <dt>org.apache.velocity.tools</dt> * <dd>Path and name of the toolbox configuration file. The path must be * relative to the web application root directory. If this parameter is * not found, the servlet will check for a toolbox file at * '/WEB-INF/tools.xml'.</dd> * <dt>org.apache.velocity.properties</dt> * <dd>Path and name of the Velocity configuration file. The path must be * relative to the web application root directory. If this parameter * is not present, Velocity will check for a properties file at * '/WEB-INF/velocity.properties'. If no file is found there, then * Velocity is initialized with the settings in the classpath at * 'org.apache.velocity.tools.view.velocity.properties'.</dd> * </dl> * * @author Dave Bryson * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> * @author <a href="mailto:sidler@teamup.com">Gabe Sidler</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a> * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> * @author Nathan Bubna * * @version $Id: VelocityView.java 511959 2007-02-26 19:24:39Z nbubna $ */ public class VelocityView extends ViewToolManager { /** The HTTP content type context key. */ public static final String CONTENT_TYPE_KEY = "default.contentType"; /** * Key used to access the ServletContext in * the Velocity application attributes. */ public static final String SERVLET_CONTEXT_KEY = ServletContext.class.getName(); /** The default content type for the response */ public static final String DEFAULT_CONTENT_TYPE = "text/html"; /** Default encoding for the output stream */ public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1"; /** * Key used to access the toolbox configuration file path from the * Servlet or webapp init parameters ("org.apache.velocity.tools") * or to access a live {@link FactoryConfiguration} previously * placed in the ServletContext attributes. */ public static final String TOOLS_KEY = ServletUtils.CONFIGURATION_KEY; @Deprecated public static final String DEPRECATED_TOOLS_KEY = "org.apache.velocity.toolbox"; /** * Default toolbox configuration file path. If no alternate value for * this is specified, the servlet will look here. */ public static final String USER_TOOLS_PATH = "/WEB-INF/tools.xml"; @Deprecated public static final String DEPRECATED_USER_TOOLS_PATH = "/WEB-INF/toolbox.xml"; /** * Default Runtime properties. */ public static final String DEFAULT_PROPERTIES_PATH = "/org/apache/velocity/tools/view/velocity.properties"; /** * This is the string that is looked for when getInitParameter is * called ("org.apache.velocity.properties"). */ public static final String PROPERTIES_KEY = "org.apache.velocity.properties"; /** * Default velocity properties file path. If no alternate value for * this is specified, the servlet will look here. */ public static final String USER_PROPERTIES_PATH = "/WEB-INF/velocity.properties"; /** * Controls loading of available default tool configurations * provided by VelocityTools. The default behavior is conditional; * if {@link #DEPRECATION_SUPPORT_MODE_KEY} has not been set to * {@code false} and there is an old {@code toolbox.xml} configuration * present, then the defaults will not be loaded unless you explicitly * set this property to {@code true} in your init params. If there * is no {@code toolbox.xml} and/or the deprecation support is turned off, * then the default tools will be loaded automatically unless you * explicitly set this property to {@code false} in your init params. */ public static final String LOAD_DEFAULTS_KEY = "org.apache.velocity.tools.loadDefaults"; /** * Controls removal of tools or data with invalid configurations * before initialization is finished. * The default is false; set to {@code true} to turn this feature on. */ public static final String CLEAN_CONFIGURATION_KEY = "org.apache.velocity.tools.cleanConfiguration"; /** * Controls whether or not templates can overwrite tool and servlet API * variables in the local context. The default is true; set to {@code false} * to prevent overwriting of any tool variables. */ public static final String USER_OVERWRITE_KEY = "org.apache.velocity.tools.userCanOverwriteTools"; /** * Controls support for deprecated tools and configuration. * The default is {@code true}; set to {@code false} to turn off * support for deprecated tools and configuration. */ public static final String DEPRECATION_SUPPORT_MODE_KEY = "org.apache.velocity.tools.deprecationSupportMode"; private static SimplePool writerPool = new SimplePool(40); private String defaultContentType = DEFAULT_CONTENT_TYPE; private boolean deprecationSupportMode = true; public VelocityView(ServletConfig config) { this(new JeeServletConfig(config)); } public VelocityView(FilterConfig config) { this(new JeeFilterConfig(config)); } public VelocityView(ServletContext context) { this(new JeeContextConfig(context)); } public VelocityView(JeeConfig config) { // suppress auto-config, as we have our own config lookup order here super(config.getServletContext(), false, false); init(config); } @Deprecated protected final void setDeprecationSupportMode(boolean support) { if (deprecationSupportMode != support) { this.deprecationSupportMode = support; debug("deprecationSupportMode is now %s", (support ? "on" : "off")); } } /** * Overrides super class to ensure engine is not set to null. */ @Override public void setVelocityEngine(VelocityEngine engine) { if (engine == null) { throw new NullPointerException("VelocityEngine cannot be null"); } super.setVelocityEngine(engine); } /** * Returns the configured default Content-Type. */ public String getDefaultContentType() { return this.defaultContentType; } /** * Sets the configured default Content-Type. */ public void setDefaultContentType(String type) { if (!defaultContentType.equals(type)) { this.defaultContentType = type; debug("Default Content-Type was changed to %s", type); } } /** * Simplifies process of getting a property from VelocityEngine, * because the VelocityEngine interface sucks compared to the singleton's. * Use of this method assumes that {@link #init(JeeConfig,VelocityEngine)} * has already been called. */ protected String getProperty(String key, String alternate) { String prop = (String) velocity.getProperty(key); if (prop == null || prop.length() == 0) { return alternate; } return prop; } /** * <p>Initializes ToolboxFactory, VelocityEngine, and sets default * encoding for processing requests.</p> * * <p>NOTE: If no charset is specified in the default.contentType * property (in your velocity.properties) and you have specified * an output.encoding property, then that will be used as the * charset for the default content-type of pages served by this * servlet.</p> * * @param config servlet configuation */ protected void init(JeeConfig config) { // create an engine if none is set yet // (servletContext and factory should already be set by now if (this.velocity == null) { this.velocity = new VelocityEngine(); } // default is true for these, so just watch for false String depMode = config.findInitParameter(DEPRECATION_SUPPORT_MODE_KEY); if (depMode != null && depMode.equalsIgnoreCase("false")) { setDeprecationSupportMode(false); } String allowOverwrite = config.findInitParameter(USER_OVERWRITE_KEY); if (allowOverwrite != null && allowOverwrite.equalsIgnoreCase("false")) { setUserCanOverwriteTools(false); } // configure and initialize the VelocityEngine init(config, velocity); // configure the ToolboxFactory configure(config, factory); // set encoding & content-type setEncoding(config); } /** * Initializes the Velocity runtime, first calling * loadConfiguration(JeeConfig) to get a * org.apache.commons.collections.ExtendedProperties * of configuration information * and then calling velocityEngine.init(). Override this * to do anything to the environment before the * initialization of the singleton takes place, or to * initialize the singleton in other ways. * * @param config servlet configuration parameters */ protected void init(JeeConfig config, final VelocityEngine velocity) { // register this engine to be the default handler of log messages // if the user points commons-logging to the LogSystemCommonsLog LogChuteCommonsLog.setVelocityLog(getLog()); // put the servlet context into Velocity's application attributes, // where the WebappResourceLoader can find them velocity.setApplicationAttribute(SERVLET_CONTEXT_KEY, this.servletContext); // configure the engine itself configure(config, velocity); // now all is ready - init Velocity try { velocity.init(); } catch (Exception e) { String msg = "Could not initialize VelocityEngine"; getLog().error(msg, e); e.printStackTrace(); throw new RuntimeException(msg + ": " + e, e); } } protected void configure(final JeeConfig config, final VelocityEngine velocity) { // first get the default properties, and bail if we don't find them ExtendedProperties defaultProperties = getProperties(DEFAULT_PROPERTIES_PATH, true); // if using Velocity engine prior to 1.6.x, remove WebappUberspector // (this hack will disappear once tools require Velocity 1.6.x+) try { Class.forName("org.apache.velocity.tools.view.WebappUberspector"); } catch (Throwable t) { // remove WebappUberspector from the list of introspectors List introspectors = defaultProperties.getList(VelocityEngine.UBERSPECT_CLASSNAME); introspectors.remove("org.apache.velocity.tools.view.WebappUberspector"); defaultProperties.setProperty(VelocityEngine.UBERSPECT_CLASSNAME, introspectors); } velocity.setExtendedProperties(defaultProperties); // check for application-wide user props in the context init params String appPropsPath = servletContext.getInitParameter(PROPERTIES_KEY); setProps(velocity, appPropsPath, true); // check for servlet-wide user props in the config init params at the // conventional location, and be silent if they're missing setProps(velocity, USER_PROPERTIES_PATH, false); // check for a custom location for servlet-wide user props String servletPropsPath = config.getInitParameter(PROPERTIES_KEY); setProps(velocity, servletPropsPath, true); } private boolean setProps(VelocityEngine velocity, String path, boolean require) { if (path == null) { // only bother with this if a path was given return false; } // this will throw an exception if require is true and there // are no properties at the path. if require is false, this // will return null when there's no properties at the path ExtendedProperties props = getProperties(path, require); if (props == null) { return false; } debug("Configuring Velocity with properties at: %s", path); // these props will override those already set velocity.setExtendedProperties(props); // notify that new props were set return true; } /** * Here's the configuration lookup/loading order: * <ol> * <li>If deprecationSupportMode is true: * <ol> * <li>Config file optionally specified by {@code org.apache.velocity.toolbox} init-param (servlet or servletContext)</li> * <li>If none, config file optionally at {@code /WEB-INF/toolbox.xml} (deprecated conventional location)</li> * </ol> * </li> * <li>If no old toolbox or loadDefaults is true, {@link ConfigurationUtils#getDefaultTools()}</li> * <li>{@link ConfigurationUtils#getAutoLoaded}(false)</li> * <li>Config file optionally specified by servletContext {@code org.apache.velocity.tools} init-param</li> * <li>Config file optionally at {@code /WEB-INF/tools.xml} (new conventional location)</li> * <li>Config file optionally specified by servlet {@code org.apache.velocity.tools} init-param</li> * </ol> * Remember that as these configurations are added on top of each other, * the newer values will always override the older ones. Also, once they * are all loaded, this method can "clean" your configuration of all invalid * tool, toolbox or data configurations if you set the * {@code org.apache.velocity.tools.cleanConfiguration} init-param to true in * either your servlet or servletContext init-params. */ protected void configure(final JeeConfig config, final ToolboxFactory factory) { FactoryConfiguration factoryConfig = new FactoryConfiguration("VelocityView.configure(config,factory)"); boolean hasOldToolbox = false; if (this.deprecationSupportMode) { FactoryConfiguration oldToolbox = getDeprecatedConfig(config); if (oldToolbox != null) { hasOldToolbox = true; factoryConfig.addConfiguration(oldToolbox); } } // only load the default tools if they have explicitly said to // or if they are not using an old toolbox and have said nothing String loadDefaults = config.findInitParameter(LOAD_DEFAULTS_KEY); if ((!hasOldToolbox && loadDefaults == null) || "true".equalsIgnoreCase(loadDefaults)) { // add all available default tools getLog().trace("Loading default tools configuration..."); factoryConfig.addConfiguration(ConfigurationUtils.getDefaultTools()); } else { // let the user know that the defaults were suppressed debug("Default tools configuration has been suppressed%s", (hasOldToolbox ? " to avoid conflicts with older application's context and toolbox definition." : ".")); } // this gets the auto loaded config from the classpath // this doesn't include defaults since they're handled already // and it could theoretically pick up an auto-loaded config from the // filesystem, but that is highly unlikely to happen in a webapp env FactoryConfiguration autoLoaded = ConfigurationUtils.getAutoLoaded(false); factoryConfig.addConfiguration(autoLoaded); // check for application-wide user config in the context init params String appToolsPath = servletContext.getInitParameter(TOOLS_KEY); setConfig(factoryConfig, appToolsPath, true); // check for user configuration at the conventional location, // and be silent if they're missing setConfig(factoryConfig, USER_TOOLS_PATH, false); // check for a custom location for servlet-wide user props String servletToolsPath = config.getInitParameter(TOOLS_KEY); setConfig(factoryConfig, servletToolsPath, true); // check for "injected" configuration in application attributes FactoryConfiguration injected = ServletUtils.getConfiguration(servletContext); if (injected != null) { debug("Adding configuration instance in servletContext attributes as '%s'", TOOLS_KEY); factoryConfig.addConfiguration(injected); } // see if we should only keep valid tools, data, and properties String cleanConfig = config.findInitParameter(CLEAN_CONFIGURATION_KEY); if ("true".equals(cleanConfig)) { // remove invalid tools, data, and properties from the configuration ConfigurationCleaner cleaner = new ConfigurationCleaner(); cleaner.setLog(getLog()); cleaner.clean(factoryConfig); } // apply this configuration to the specified factory debug("Configuring factory with: %s", factoryConfig); configure(factoryConfig); } /** * First tries to find a path to a toolbox under the deprecated * {@code org.apache.velocity.toolbox} key. * If found, it tries to load the configuration there and will blow up * if there is no config file there. * If not found, it looks for a config file at /WEB-INF/toolbox.xml * (the deprecated default location) and tries to load it if found. */ @Deprecated protected FactoryConfiguration getDeprecatedConfig(JeeConfig config) { FactoryConfiguration toolbox = null; // look for specified path under the deprecated toolbox key String oldPath = config.findInitParameter(DEPRECATED_TOOLS_KEY); if (oldPath != null) { // ok, they said the toolbox.xml should be there // so this should blow up if it is not toolbox = getConfiguration(oldPath, true); } else { // check for deprecated user configuration at the old conventional // location. be silent if missing, log deprecation warning otherwise oldPath = DEPRECATED_USER_TOOLS_PATH; toolbox = getConfiguration(oldPath); } if (toolbox != null) { debug("Loaded deprecated configuration from: %s", oldPath); getLog().warn("Please upgrade to new \"/WEB-INF/tools.xml\" format and conventional location." + " Support for \"/WEB-INF/toolbox.xml\" format and conventional file name will " + "be removed in a future version."); } return toolbox; } private boolean setConfig(FactoryConfiguration factory, String path, boolean require) { if (path == null) { // only bother with this if a path was given return false; } // this will throw an exception if require is true and there // is no tool config at the path. if require is false, this // will return null when there's no tool config at the path FactoryConfiguration config = getConfiguration(path, require); if (config == null) { return false; } debug("Loaded configuration from: %s", path); factory.addConfiguration(config); // notify that new config was added return true; } protected InputStream getInputStream(String path, boolean required) { // first, search the classpath InputStream inputStream = ServletUtils.getInputStream(path, this.servletContext); // if we didn't find one if (inputStream == null) { String msg = "Did not find resource at: " + path; if (required) { getLog().error(msg); throw new ResourceNotFoundException(msg); } debug(msg); return null; } return inputStream; } protected ExtendedProperties getProperties(String path) { return getProperties(path, false); } protected ExtendedProperties getProperties(String path, boolean required) { if (getLog().isTraceEnabled()) { getLog().trace("Searching for properties at: " + path); } InputStream inputStream = getInputStream(path, required); if (inputStream == null) { return null; } ExtendedProperties properties = new ExtendedProperties(); try { properties.load(inputStream); } catch (IOException ioe) { String msg = "Failed to load properties at: " + path; getLog().error(msg, ioe); if (required) { throw new RuntimeException(msg, ioe); } } finally { try { if (inputStream != null) { inputStream.close(); } } catch (IOException ioe) { getLog().error("Failed to close input stream for " + path, ioe); } } return properties; } protected FactoryConfiguration getConfiguration(String path) { return getConfiguration(path, false); } protected FactoryConfiguration getConfiguration(String path, boolean required) { if (getLog().isTraceEnabled()) { getLog().trace("Searching for configuration at: " + path); } FactoryConfiguration config = null; try { config = ServletUtils.getConfiguration(path, this.servletContext, this.deprecationSupportMode); if (config == null) { String msg = "Did not find resource at: " + path; if (required) { getLog().error(msg); throw new ResourceNotFoundException(msg); } else { debug(msg); } } } catch (ResourceNotFoundException rnfe) { // no need to re-log this throw rnfe; } catch (RuntimeException re) { if (required) { getLog().error(re.getMessage(), re); throw re; } getLog().debug(re.getMessage(), re); } return config; } protected void setEncoding(JeeConfig config) { // we can get these now that velocity is initialized this.defaultContentType = getProperty(CONTENT_TYPE_KEY, DEFAULT_CONTENT_TYPE); String encoding = getProperty(RuntimeConstants.OUTPUT_ENCODING, DEFAULT_OUTPUT_ENCODING); // For non Latin-1 encodings, ensure that the charset is // included in the Content-Type header. if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) { int index = defaultContentType.lastIndexOf("charset"); if (index < 0) { // the charset specifier is not yet present in header. // append character encoding to default content-type this.defaultContentType += "; charset=" + encoding; } else { // The user may have configuration issues. getLog().info("Charset was already " + "specified in the Content-Type property. " + "Output encoding property will be ignored."); } } debug("Default Content-Type is: %s", defaultContentType); } /******************* REQUEST PROCESSING ****************************/ /** * * * @param request HttpServletRequest object containing client request * @param response HttpServletResponse object for the response * @return the {@link Context} prepared and used to perform the rendering * to allow proper cleanup afterward */ public Context render(HttpServletRequest request, HttpServletResponse response) throws IOException { // then get a context Context context = createContext(request, response); // get the template Template template = getTemplate(request, response); // merge the template and context into the response merge(template, context, response.getWriter()); return context; } public Context render(HttpServletRequest request, Writer out) throws IOException { // then get a context Context context = createContext(request, null); // get the template Template template = getTemplate(request); // merge the template and context into the writer merge(template, context, out); return context; } /** * <p>Creates and returns an initialized Velocity context.</p> * * A new context of class {@link ViewToolContext} is created and * initialized. * * @param request servlet request from client * @param response servlet reponse to client */ @Override public ViewToolContext createContext(HttpServletRequest request, HttpServletResponse response) { ViewToolContext ctx; if (this.deprecationSupportMode) { ctx = new ChainedContext(velocity, request, response, servletContext); } else { ctx = new ViewToolContext(velocity, request, response, servletContext); } prepareContext(ctx, request); return ctx; } /** * <p>Gets the requested template.</p> * * @param request client request * @return Velocity Template object or null */ public Template getTemplate(HttpServletRequest request) { return getTemplate(request, null); } public Template getTemplate(HttpServletRequest request, HttpServletResponse response) { String path = ServletUtils.getPath(request); if (response == null) { return getTemplate(path); } else { return getTemplate(path, response.getCharacterEncoding()); } } /** * Retrieves the requested template. * * @param name The file name of the template to retrieve relative to the * template root. * @return The requested template. * @throws ResourceNotFoundException if template not found * from any available source. * @throws ParseErrorException if template cannot be parsed due * to syntax (or other) error. */ public Template getTemplate(String name) { return getTemplate(name, null); } /** * Retrieves the requested template with the specified character encoding. * * @param name The file name of the template to retrieve relative to the * template root. * @param encoding the character encoding of the template * @return The requested template. * @throws ResourceNotFoundException if template not found * from any available source. * @throws ParseErrorException if template cannot be parsed due * to syntax (or other) error. */ public Template getTemplate(String name, String encoding) { try { if (encoding == null) { return velocity.getTemplate(name); } else { return velocity.getTemplate(name, encoding); } } catch (RuntimeException e) // FIXME This is useless with Velocity 1.7 { throw e; } catch (Exception e) // FIXME This is useless with Velocity 1.7 { throw new RuntimeException(e); } } /** * Merges the template with the context. Only override this if you really, really * really need to. (And don't call us with questions if it breaks :) * * @param template template being rendered * @param context Context created by the {@link #createContext} * @param writer into which the content is rendered */ public void merge(Template template, Context context, Writer writer) throws IOException { VelocityWriter vw = null; try { vw = (VelocityWriter) writerPool.get(); if (vw == null) { vw = new VelocityWriter(writer, 4 * 1024, true); } else { vw.recycle(writer); } performMerge(template, context, vw); // flush writer but don't close to allow us to play nicely with others. vw.flush(); } finally { if (vw != null) { try { /* This hack sets the VelocityWriter's internal ref to the * PrintWriter to null to keep memory free while * the writer is pooled. See bug report #18951 */ vw.recycle(null); writerPool.put(vw); } catch (Exception e) { getLog().error("Trouble releasing VelocityWriter: " + e.getMessage(), e); } } } } /** * This is here so developers may override it and gain access to the * Writer which the template will be merged into. See * <a href="http://issues.apache.org/jira/browse/VELTOOLS-7">VELTOOLS-7</a> * for discussion of this. * * @param template template object returned by the handleRequest() method * @param context Context created by the {@link #createContext} * @param writer a VelocityWriter that the template is merged into */ protected void performMerge(Template template, Context context, Writer writer) throws IOException { template.merge(context, writer); } }