/* * Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky * * Licensed 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. */ package freemarker.ext.servlet; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.regex.Pattern; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import freemarker.cache.ClassTemplateLoader; import freemarker.cache.FileTemplateLoader; import freemarker.cache.MultiTemplateLoader; import freemarker.cache.TemplateLoader; import freemarker.cache.WebappTemplateLoader; import freemarker.core.Configurable; import freemarker.ext.jsp.TaglibFactory; import freemarker.ext.jsp.TaglibFactory.ClasspathMetaInfTldSource; import freemarker.ext.jsp.TaglibFactory.ClearMetaInfTldSource; import freemarker.ext.jsp.TaglibFactory.MetaInfTldSource; import freemarker.ext.jsp.TaglibFactory.WebInfPerLibJarMetaInfTldSource; import freemarker.log.Logger; import freemarker.template.Configuration; import freemarker.template.ObjectWrapper; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; import freemarker.template.TemplateNotFoundException; import freemarker.template.utility.SecurityUtilities; import freemarker.template.utility.StringUtil; /** * FreeMarker MVC View servlet that can be used similarly to JSP views. That is, you put the variables to expose into * HTTP servlet request attributes, then forward to an FTL file (instead of to a JSP file) that's mapped to this servet * (usually via the {@code <url-pattern>*.ftl<url-pattern>}). See web.xml example (and more) in the FreeMarker Manual! * * * <p> * <b>Main features</b> * </p> * * * <ul> * * <li>It makes all request, request parameters, session, and servlet context attributes available to templates through * <code>Request</code>, <code>RequestParameters</code>, <code>Session</code>, and <code>Application</code> variables. * * <li>The scope variables are also available via automatic scope discovery. That is, writing * <code>Application.attrName</code>, <code>Session.attrName</code>, <code>Request.attrName</code> is not mandatory; * it's enough to write <code>attrName</code>, and if no such variable was created in the template, it will search the * variable in <code>Request</code>, and then in <code>Session</code>, and finally in <code>Application</code>. * * <li>It creates a variable with name <code>JspTaglibs</code> that can be used to load JSP taglibs. For example:<br> * <code><#assign dt=JspTaglibs["http://displaytag.sf.net"]></code> or * <code><#assign tiles=JspTaglibs["/WEB-INF/struts-tiles.tld"]></code>. * * <li>A custom directive named {@code include_page} allows you to include the output of another servlet resource from * your servlet container, just as if you used {@code ServletRequest.getRequestDispatcher(path).include()}: * {@code <@include_page path="/myWebapp/somePage.jsp"/>}. You can also pass parameters to the newly included page by * passing a hash named {@code params}: * <code><@include_page path="/myWebapp/somePage.jsp" params= lang: "en", q="5"}/></code>. By default, the request * parameters of the original request (the one being processed by FreemarkerServlet) are also inherited by the include. * You can explicitly control this inheritance using the {@code inherit_params} parameter: * <code><@include_page path="/myWebapp/somePage.jsp" params={lang: "en", q="5"} inherit_params=false/></code>. * * </ul> * * * <p> * <b>Supported {@code init-param}-s</b> * </p> * * * <ul> * * <li><strong>{@value #INIT_PARAM_TEMPLATE_PATH}</strong>: Specifies the location of the templates. By default, this is * interpreted as a {@link ServletContext} reasource path, which practically means a web application directory relative * path, or a {@code WEB-INF/lib/*.jar/META-INF/resources}-relative path (note that this last didn't work properly * before FreeMarker 2.3.23).<br> * Alternatively, you can prepend it with <tt>file://</tt> to indicate a literal path in the file system (i.e. * <tt>file:///var/www/project/templates/</tt>). Note that three slashes were used to specify an absolute path.<br> * Also, you can prepend it with {@code classpath:}, like in <tt>classpath:com/example/templates</tt>, to indicate that * you want to load templates from the specified package accessible through the Thread Context Class Loader of the * thread that initializes this servlet.<br> * If {@code incompatible_improvements} is set to 2.3.22 (or higher), you can specify multiple comma separated locations * inside square brackets, like: {@code [ WEB-INF/templates, classpath:com/example/myapp/templates ]}. * This internally creates a {@link MultiTemplateLoader}. Note again that if {@code incompatible_improvements} isn't * set to at least 2.3.22, the initial {@code [} has no special meaning, and so this feature is unavailable.<br> * Any of the above can have a {@code ?setting(name=value, ...)} postfix to set the JavaBeans properties of the * {@link TemplateLoader} created. For example, * {@code /templates?settings(attemptFileAccess=false, URLConnectionUsesCaches=true)} * calls {@link WebappTemplateLoader#setAttemptFileAccess(boolean)} * and {@link WebappTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune the {@link WebappTemplateLoader}. * For backward compatibility (not recommended!), you can use the {@code class://} prefix, like in * <tt>class://com/example/templates</tt> format, which is similar to {@code classpath:}, except that it uses the * defining class loader of this servlet's class. This can cause template not found errors, if that class (in * {@code freemarer.jar} usually) is not local to the web application, while the templates are.<br> * The default value is <tt>class://</tt> (that is, the root of the class hierarchy), which is not recommended anymore, * and should be overwritten with the init-param.</li> * * <li><strong>{@value #INIT_PARAM_NO_CACHE}</strong>: If set to true, generates headers in the response that advise the * HTTP client not to cache the returned page. The default is <tt>false</tt>.</li> * * <li><strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong>: If specified, response uses the specified Content-type HTTP * header. The value may include the charset (e.g. <tt>"text/html; charset=ISO-8859-1"</tt>). If not specified, * <tt>"text/html"</tt> is used. If the charset is not specified in this init-param, then the charset (encoding) of the * actual template file will be used (in the response HTTP header and for encoding the output stream). Note that this * setting can be overridden on a per-template basis by specifying a custom attribute named <tt>content_type</tt> in the * <tt>attributes</tt> parameter of the <tt><#ftl></tt> directive.</li> * * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes. * This corresponds to {@link HttpServletResponse#setBufferSize(int)}. If the {@link HttpServletResponse} state doesn't * allow changing the buffer size, it will silently do nothing. If this init param isn't specified, then the buffer size * is not set by {@link FreemarkerServlet} in the HTTP response, which usually means that the default buffer size of the * servlet container will be used.</li> * * <li><strong>{@value #INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE}</strong> (since 2.3.22): If {@code false} (default, * but not recommended), if a template is requested that's missing, this servlet responses with a HTTP 404 "Not found" * error, and only logs the problem with debug level. If {@code true} (recommended), the servlet will log the issue with * error level, then throws an exception that bubbles up to the servlet container, which usually then creates a HTTP 500 * "Internal server error" response (and maybe logs the event into the container log). See "Error handling" later for * more!</li> * * <li><strong>{@value #INIT_PARAM_META_INF_TLD_LOCATIONS}</strong> (since 2.3.22): Comma separated list of items, each * is either {@value #META_INF_TLD_LOCATION_WEB_INF_PER_LIB_JARS}, or {@value #META_INF_TLD_LOCATION_CLASSPATH} * optionally followed by colon and a regular expression, or {@value #META_INF_TLD_LOCATION_CLEAR}. For example * {@code <param-value>classpath:.*myoverride.*\.jar$, webInfPerLibJars, classpath:.*taglib.*\.jar$</param-value>}, or * {@code <param-value>classpath</param-value>}. (Whitespace around the commas and list items will be ignored.) See * {@link TaglibFactory#setMetaInfTldSources(List)} for more information. Defaults to a list that contains * {@value #META_INF_TLD_LOCATION_WEB_INF_PER_LIB_JARS} only (can be overridden with * {@link #createDefaultMetaInfTldSources()}). Note that this can be also specified with the * {@value #SYSTEM_PROPERTY_META_INF_TLD_SOURCES} system property. If both the init-param and the system property * exists, the sources listed in the system property will be added after those specified by the init-param. This is * where the special entry, {@value #META_INF_TLD_LOCATION_CLEAR} comes handy, as it will remove all previous list * items. (An intended usage of the system property is setting it to {@code clear, classpath} in the Eclipse run * configuration if you are running the application without putting the dependency jar-s into {@code WEB-INF/lib}.) * Also, note that further {@code classpath:<pattern>} items are added automatically at the end of this list based on * Jetty's {@code "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern"} servlet context attribute.</li> * * <li><strong>{@value #INIT_PARAM_CLASSPATH_TLDS}</strong> (since 2.3.22): Comma separated list of paths; see * {@link TaglibFactory#setClasspathTlds(List)}. Whitespace around the list items will be ignored. Defaults to no paths * (can be overidden with {@link #createDefaultClassPathTlds()}). Note that this can also be specified with the * {@value #SYSTEM_PROPERTY_CLASSPATH_TLDS} system property. If both the init-param and the system property exists, the * items listed in system property will be added after those specified by the init-param.</li> * * <li><strong>"Debug"</strong>: Deprecated, has no effect since 2.3.22. (Earlier it has enabled/disabled sending * debug-level log messages to the servlet container log, but this servlet doesn't log debug level messages into the * servlet container log anymore, only into the FreeMarker log.)</li> * * <li>The following init-params are supported only for backward compatibility, and their usage is discouraged: * {@code TemplateUpdateInterval}, {@code DefaultEncoding}, {@code ObjectWrapper}, {@code TemplateExceptionHandler}. * Instead, use init-params with the setting names documented at {@link Configuration#setSetting(String, String)}, such * as {@code object_wrapper}. * * <li><strong>Any other init-params</strong> will be interpreted as {@link Configuration}-level FreeMarker setting. See * the possible names and values at {@link Configuration#setSetting(String, String)}.</li> * * </ul> * * * <p> * <b>Error handling</b> * </p> * * * <p> * Notes: * </p> * * <ul> * * <li>Logging below, where not said otherwise, always refers to logging with FreeMarker's logging facility (see * {@link Logger}), under the "freemarker.servlet" category.</li> * <li>Throwing a {@link ServletException} to the servlet container is mentioned at a few places below. That in practice * usually means HTTP 500 "Internal server error" response, and maybe a log entry in the servlet container's log.</li> * </ul> * * <p> * Errors types: * </p> * * <ul> * * <li>If the servlet initialization fails, the servlet won't be started as usual. The cause is usually logged with * error level. When it isn't, check the servlet container's log. * * <li>If the requested template doesn't exist, by default the servlet returns a HTTP 404 "Not found" response, and logs * the problem with <em>debug</em> level. Responding with HTTP 404 is how JSP behaves, but it's actually not a * recommended setting anymore. By setting {@value #INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE} init-param to {@code true} * (recommended), it will instead log the problem with error level, then the servlet throws {@link ServletException} to * the servlet container (with the proper cause exception). After all, if the visited URL had an associated "action" but * the template behind it is missing, that's an internal server error, not a wrong URL.</li> * * <li> * If the template contains parsing errors, it will log it with error level, then the servlet throws * {@link ServletException} to the servlet container (with the proper cause exception).</li> * * <li> * If the template throws exception during its execution, and the value of the {@code template_exception_handler} * init-param is {@code rethrow} (recommended), it will log it with error level and then the servlet throws * {@link ServletException} to the servlet container (with the proper cause exception). But beware, the default value of * the {@code template_exception_handler} init-param is {@code html_debug}, which is for development only! Set it to * {@code rethrow} for production. The {@code html_debug} (and {@code debug}) handlers will print error details to the * page and then commit the HTTP response with response code 200 "OK", thus, the server wont be able roll back the * response and send back an HTTP 500 page. This is so that the template developers will see the error without digging * the logs. * * </ul> */ public class FreemarkerServlet extends HttpServlet { private static final Logger LOG = Logger.getLogger("freemarker.servlet"); private static final Logger LOG_RT = Logger.getLogger("freemarker.runtime"); public static final long serialVersionUID = -2440216393145762479L; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. (This init-param * has existed long before 2.3.22, but this constant was only added then.) * * @since 2.3.22 */ public static final String INIT_PARAM_TEMPLATE_PATH = "TemplatePath"; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. (This init-param * has existed long before 2.3.22, but this constant was only added then.) * * @since 2.3.22 */ public static final String INIT_PARAM_NO_CACHE = "NoCache"; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. (This init-param * has existed long before 2.3.22, but this constant was only added then.) * * @since 2.3.22 */ public static final String INIT_PARAM_CONTENT_TYPE = "ContentType"; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. * * @since 2.3.22 */ public static final String INIT_PARAM_BUFFER_SIZE = "BufferSize"; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. * * @since 2.3.22 */ public static final String INIT_PARAM_META_INF_TLD_LOCATIONS = "MetaInfTldSources"; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. * * @since 2.3.22 */ public static final String INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE = "ExceptionOnMissingTemplate"; /** * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params. * * @since 2.3.22 */ public static final String INIT_PARAM_CLASSPATH_TLDS = "ClasspathTlds"; private static final String INIT_PARAM_DEBUG = "Debug"; private static final String DEPR_INITPARAM_TEMPLATE_DELAY = "TemplateDelay"; private static final String DEPR_INITPARAM_ENCODING = "DefaultEncoding"; private static final String DEPR_INITPARAM_OBJECT_WRAPPER = "ObjectWrapper"; private static final String DEPR_INITPARAM_WRAPPER_SIMPLE = "simple"; private static final String DEPR_INITPARAM_WRAPPER_BEANS = "beans"; private static final String DEPR_INITPARAM_WRAPPER_JYTHON = "jython"; private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER = "TemplateExceptionHandler"; private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW = "rethrow"; private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG = "debug"; private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_HTML_DEBUG = "htmlDebug"; private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE = "ignore"; private static final String DEPR_INITPARAM_DEBUG = "debug"; private static final String DEFAULT_CONTENT_TYPE = "text/html"; /** * When set, the items defined in it will be added after those coming from the * {@value #INIT_PARAM_META_INF_TLD_LOCATIONS} init-param. The value syntax is the same as of the init-param. Note * that {@value #META_INF_TLD_LOCATION_CLEAR} can be used to re-start the list, rather than continue it. * * @since 2.3.22 */ public static final String SYSTEM_PROPERTY_META_INF_TLD_SOURCES = "org.freemarker.jsp.metaInfTldSources"; /** * When set, the items defined in it will be added after those coming from the * {@value #INIT_PARAM_CLASSPATH_TLDS} init-param. The value syntax is the same as of the init-param. * * @since 2.3.22 */ public static final String SYSTEM_PROPERTY_CLASSPATH_TLDS = "org.freemarker.jsp.classpathTlds"; /** * Used as part of the value of the {@value #INIT_PARAM_META_INF_TLD_LOCATIONS} init-param. * * @since 2.3.22 */ public static final String META_INF_TLD_LOCATION_WEB_INF_PER_LIB_JARS = "webInfPerLibJars"; /** * Used as part of the value of the {@value #INIT_PARAM_META_INF_TLD_LOCATIONS} init-param. * * @since 2.3.22 */ public static final String META_INF_TLD_LOCATION_CLASSPATH = "classpath"; /** * Used as part of the value of the {@value #INIT_PARAM_META_INF_TLD_LOCATIONS} init-param. * * @since 2.3.22 */ public static final String META_INF_TLD_LOCATION_CLEAR = "clear"; public static final String KEY_REQUEST = "Request"; public static final String KEY_INCLUDE = "include_page"; public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__"; public static final String KEY_REQUEST_PARAMETERS = "RequestParameters"; public static final String KEY_SESSION = "Session"; public static final String KEY_APPLICATION = "Application"; public static final String KEY_APPLICATION_PRIVATE = "__FreeMarkerServlet.Application__"; public static final String KEY_JSP_TAGLIBS = "JspTaglibs"; // Note these names start with dot, so they're essentially invisible from // a freemarker script. private static final String ATTR_REQUEST_MODEL = ".freemarker.Request"; private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters"; private static final String ATTR_SESSION_MODEL = ".freemarker.Session"; /** @deprecated We only keeps this attribute for backward compatibility, but actually aren't using it. */ @Deprecated private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application"; /** @deprecated We only keeps this attribute for backward compatibility, but actually aren't using it. */ @Deprecated private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs"; private static final String ATTR_JETTY_CP_TAGLIB_JAR_PATTERNS = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern"; private static final String EXPIRATION_DATE; static { // Generate expiration date that is one year from now in the past GregorianCalendar expiration = new GregorianCalendar(); expiration.roll(Calendar.YEAR, -1); SimpleDateFormat httpDate = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss z", java.util.Locale.US); EXPIRATION_DATE = httpDate.format(expiration.getTime()); } // Init-param values: private String templatePath; private boolean noCache; private Integer bufferSize; private boolean exceptionOnMissingTemplate; /** * @deprecated Not used anymore; to enable/disable debug logging, just set the logging level of the logging library * used by {@link Logger}. */ @Deprecated protected boolean debug; @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable") private Configuration config; @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable") private ObjectWrapper wrapper; private String contentType; private boolean noCharsetInContentType; private List/*<MetaInfTldSource>*/ metaInfTldSources; private List/*<String>*/ classpathTlds; private Object lazyInitFieldsLock = new Object(); @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable") private ServletContextHashModel servletContextModel; @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable") private TaglibFactory taglibFactory; private boolean objectWrapperMismatchWarnLogged; /** * Don't override this method to adjust FreeMarker settings! Override the protected methods for that, such as * {@link #createConfiguration()}, {@link #createTemplateLoader(String)}, {@link #createDefaultObjectWrapper()}, * etc. Also note that lot of things can be changed with init-params instead of overriding methods, so if you * override settings, usually you should only override their defaults. */ @Override public void init() throws ServletException { try { initialize(); } catch (Exception e) { // At least Jetty doesn't log the ServletException itself, only its cause exception. Thus we add some // message here that (re)states the obvious. throw new ServletException("Error while initializing " + this.getClass().getName() + " servlet; see cause exception.", e); } } private void initialize() throws InitParamValueException, MalformedWebXmlException, ConflictingInitParamsException { config = createConfiguration(); // Only override what's coming from the config if it was explicitly specified: final String iciInitParamValue = getInitParameter(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY); if (iciInitParamValue != null) { try { config.setSetting(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY, iciInitParamValue); } catch (Exception e) { throw new InitParamValueException(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY, iciInitParamValue, e); } } // Set FreemarkerServlet-specific defaults, except where createConfiguration() has already set them: if (!config.isTemplateExceptionHandlerExplicitlySet()) { config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); } if (!config.isLogTemplateExceptionsExplicitlySet()) { config.setLogTemplateExceptions(false); } contentType = DEFAULT_CONTENT_TYPE; // Process object_wrapper init-param out of order: wrapper = createObjectWrapper(); if (LOG.isDebugEnabled()) { LOG.debug("Using object wrapper: " + wrapper); } config.setObjectWrapper(wrapper); // Process TemplatePath init-param out of order: templatePath = getInitParameter(INIT_PARAM_TEMPLATE_PATH); if (templatePath == null && !config.isTemplateLoaderExplicitlySet()) { templatePath = InitParamParser.TEMPLATE_PATH_PREFIX_CLASS; } if (templatePath != null) { try { config.setTemplateLoader(createTemplateLoader(templatePath)); } catch (Exception e) { throw new InitParamValueException(INIT_PARAM_TEMPLATE_PATH, templatePath, e); } } metaInfTldSources = createDefaultMetaInfTldSources(); classpathTlds = createDefaultClassPathTlds(); // Process all other init-params: Enumeration initpnames = getServletConfig().getInitParameterNames(); while (initpnames.hasMoreElements()) { final String name = (String) initpnames.nextElement(); final String value = getInitParameter(name); if (name == null) { throw new MalformedWebXmlException( "init-param without param-name. " + "Maybe the web.xml is not well-formed?"); } if (value == null) { throw new MalformedWebXmlException( "init-param " + StringUtil.jQuote(name) + " without param-value. " + "Maybe the web.xml is not well-formed?"); } try { if (name.equals(DEPR_INITPARAM_OBJECT_WRAPPER) || name.equals(Configurable.OBJECT_WRAPPER_KEY) || name.equals(INIT_PARAM_TEMPLATE_PATH) || name.equals(Configuration.INCOMPATIBLE_IMPROVEMENTS)) { // ignore: we have already processed these } else if (name.equals(DEPR_INITPARAM_ENCODING)) { // BC if (getInitParameter(Configuration.DEFAULT_ENCODING_KEY) != null) { throw new ConflictingInitParamsException( Configuration.DEFAULT_ENCODING_KEY, DEPR_INITPARAM_ENCODING); } config.setDefaultEncoding(value); } else if (name.equals(DEPR_INITPARAM_TEMPLATE_DELAY)) { // BC if (getInitParameter(Configuration.TEMPLATE_UPDATE_DELAY_KEY) != null) { throw new ConflictingInitParamsException( Configuration.TEMPLATE_UPDATE_DELAY_KEY, DEPR_INITPARAM_TEMPLATE_DELAY); } try { config.setTemplateUpdateDelay(Integer.parseInt(value)); } catch (NumberFormatException e) { // Intentionally ignored } } else if (name.equals(DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER)) { // BC if (getInitParameter(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY) != null) { throw new ConflictingInitParamsException( Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY, DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER); } if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW.equals(value)) { config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG.equals(value)) { config.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_HTML_DEBUG.equals(value)) { config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE.equals(value)) { config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER); } else { throw new InitParamValueException(DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER, value, "Not one of the supported values."); } } else if (name.equals(INIT_PARAM_NO_CACHE)) { noCache = StringUtil.getYesNo(value); } else if (name.equals(INIT_PARAM_BUFFER_SIZE)) { bufferSize = Integer.valueOf(parseSize(value)); } else if (name.equals(DEPR_INITPARAM_DEBUG)) { // BC if (getInitParameter(INIT_PARAM_DEBUG) != null) { throw new ConflictingInitParamsException(INIT_PARAM_DEBUG, DEPR_INITPARAM_DEBUG); } debug = StringUtil.getYesNo(value); } else if (name.equals(INIT_PARAM_DEBUG)) { debug = StringUtil.getYesNo(value); } else if (name.equals(INIT_PARAM_CONTENT_TYPE)) { contentType = value; } else if (name.equals(INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE)) { exceptionOnMissingTemplate = StringUtil.getYesNo(value); } else if (name.equals(INIT_PARAM_META_INF_TLD_LOCATIONS)) {; metaInfTldSources = parseAsMetaInfTldLocations(value); } else if (name.equals(INIT_PARAM_CLASSPATH_TLDS)) {; List newClasspathTlds = new ArrayList(); if (classpathTlds != null) { newClasspathTlds.addAll(classpathTlds); } newClasspathTlds.addAll(InitParamParser.parseCommaSeparatedList(value)); classpathTlds = newClasspathTlds; } else { config.setSetting(name, value); } } catch (ConflictingInitParamsException e) { throw e; } catch (Exception e) { throw new InitParamValueException(name, value, e); } } // while initpnames noCharsetInContentType = true; int i = contentType.toLowerCase().indexOf("charset="); if (i != -1) { char c = ' '; i--; while (i >= 0) { c = contentType.charAt(i); if (!Character.isWhitespace(c)) break; i--; } if (i == -1 || c == ';') { noCharsetInContentType = false; } } } private List/*<MetaInfTldSource>*/ parseAsMetaInfTldLocations(String value) throws ParseException { List/*<MetaInfTldSource>*/ metaInfTldSources = null; List/*<String>*/ values = InitParamParser.parseCommaSeparatedList(value); for (Iterator it = values.iterator(); it.hasNext(); ) { final String itemStr = (String) it.next(); final MetaInfTldSource metaInfTldSource; if (itemStr.equals(META_INF_TLD_LOCATION_WEB_INF_PER_LIB_JARS)) { metaInfTldSource = WebInfPerLibJarMetaInfTldSource.INSTANCE; } else if (itemStr.startsWith(META_INF_TLD_LOCATION_CLASSPATH)) { String itemRightSide = itemStr.substring(META_INF_TLD_LOCATION_CLASSPATH.length()).trim(); if (itemRightSide.length() == 0) { metaInfTldSource = new ClasspathMetaInfTldSource(Pattern.compile(".*", Pattern.DOTALL)); } else if (itemRightSide.startsWith(":")) { final String regexpStr = itemRightSide.substring(1).trim(); if (regexpStr.length() == 0) { throw new ParseException("Empty regular expression after \"" + META_INF_TLD_LOCATION_CLASSPATH + ":\"", -1); } metaInfTldSource = new ClasspathMetaInfTldSource(Pattern.compile(regexpStr)); } else { throw new ParseException("Invalid \"" + META_INF_TLD_LOCATION_CLASSPATH + "\" value syntax: " + value, -1); } } else if (itemStr.startsWith(META_INF_TLD_LOCATION_CLEAR)) { metaInfTldSource = ClearMetaInfTldSource.INSTANCE; } else { throw new ParseException("Item has no recognized source type prefix: " + itemStr, -1); } if (metaInfTldSources == null) { metaInfTldSources = new ArrayList(); } metaInfTldSources.add(metaInfTldSource); } return metaInfTldSources; } /** * Create the template loader. The default implementation will create a {@link ClassTemplateLoader} if the template * path starts with {@code "class://"}, a {@link FileTemplateLoader} if the template path starts with * {@code "file://"}, and a {@link WebappTemplateLoader} otherwise. Also, if * {@link Configuration#Configuration(freemarker.template.Version) incompatible_improvements} is 2.3.22 or higher, * it will create a {@link MultiTemplateLoader} if the template path starts with {@code "["}. * * @param templatePath * the template path to create a loader for * @return a newly created template loader */ protected TemplateLoader createTemplateLoader(String templatePath) throws IOException { return InitParamParser.createTemplateLoader(templatePath, getConfiguration(), getClass(), getServletContext()); } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { process(request, response); } @Override public void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { process(request, response); } private void process( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Give chance to subclasses to perform preprocessing if (preprocessRequest(request, response)) { return; } if (bufferSize != null && !response.isCommitted()) { try { response.setBufferSize(bufferSize.intValue()); } catch (IllegalStateException e) { LOG.debug("Can't set buffer size any more,", e); } } String templatePath = requestUrlToTemplatePath(request); if (LOG.isDebugEnabled()) { LOG.debug("Requested template " + StringUtil.jQuoteNoXSS(templatePath) + "."); } final Locale locale = deduceLocale(templatePath, request, response); final Template template; try { template = config.getTemplate(templatePath, locale); } catch (TemplateNotFoundException e) { if (exceptionOnMissingTemplate) { throw newServletExceptionWithFreeMarkerLogging( "Template not found for name " + StringUtil.jQuoteNoXSS(templatePath) + ".", e); } else { if (LOG.isDebugEnabled()) { LOG.debug("Responding HTTP 404 \"Not found\" for missing template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e); } response.sendError(HttpServletResponse.SC_NOT_FOUND, "Page template not found"); return; } } catch (freemarker.core.ParseException e) { throw newServletExceptionWithFreeMarkerLogging( "Parsing error with template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e); } catch (Exception e) { throw newServletExceptionWithFreeMarkerLogging( "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e); } Object attrContentType = template.getCustomAttribute("content_type"); if (attrContentType != null) { response.setContentType(attrContentType.toString()); } else { if (noCharsetInContentType) { response.setContentType(contentType + "; charset=" + template.getEncoding()); } else { response.setContentType(contentType); } } setBrowserCachingPolicy(response); ServletContext servletContext = getServletContext(); try { logWarnOnObjectWrapperMismatch(); TemplateModel model = createModel(wrapper, servletContext, request, response); // Give subclasses a chance to hook into preprocessing if (preTemplateProcess(request, response, template, model)) { try { // Process the template template.process(model, response.getWriter()); } finally { // Give subclasses a chance to hook into postprocessing postTemplateProcess(request, response, template, model); } } } catch (TemplateException e) { final TemplateExceptionHandler teh = config.getTemplateExceptionHandler(); // Ensure that debug handler responses aren't rolled back: if (teh == TemplateExceptionHandler.HTML_DEBUG_HANDLER || teh == TemplateExceptionHandler.DEBUG_HANDLER || teh.getClass().getName().indexOf("Debug") != -1) { response.flushBuffer(); } throw newServletExceptionWithFreeMarkerLogging("Error executing FreeMarker template", e); } } private ServletException newServletExceptionWithFreeMarkerLogging(String message, Throwable cause) throws ServletException { if (cause instanceof TemplateException) { // For backward compatibility, we log into the same category as Environment did when // log_template_exceptions was true. LOG_RT.error(message, cause); } else { LOG.error(message, cause); } ServletException e = new ServletException(message, cause); try { // Prior to Servlet 2.5, the cause exception wasn't set by the above constructor. // If we are on 2.5+ then this will throw an exception as the cause was already set. e.initCause(cause); } catch (Exception ex) { // Ignored; see above } throw e; } @SuppressFBWarnings(value={ "MSF_MUTABLE_SERVLET_FIELD", "DC_DOUBLECHECK" }, justification="Performance trick") private void logWarnOnObjectWrapperMismatch() { // Deliberately uses double check locking. if (wrapper != config.getObjectWrapper() && !objectWrapperMismatchWarnLogged && LOG.isWarnEnabled()) { final boolean logWarn; synchronized (this) { logWarn = !objectWrapperMismatchWarnLogged; if (logWarn) { objectWrapperMismatchWarnLogged = true; } } if (logWarn) { LOG.warn( this.getClass().getName() + ".wrapper != config.getObjectWrapper(); possibly the result of incorrect extension of " + FreemarkerServlet.class.getName() + "."); } } } /** * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call. The base implementation * simply returns the locale setting of the configuration. Override this method to provide different behaviour, i.e. * to use the locale indicated in the request. * * @param templatePath * The template path (templat name) as it will be passed to {@link Configuration#getTemplate(String)}. * (Not to be confused with the servlet init-param of identical name; they aren't related.) * * @throws ServletException * Can be thrown since 2.3.22, if the locale can't be deduced from the URL. */ protected Locale deduceLocale(String templatePath, HttpServletRequest request, HttpServletResponse response) throws ServletException { return config.getLocale(); } protected TemplateModel createModel(ObjectWrapper objectWrapper, ServletContext servletContext, final HttpServletRequest request, final HttpServletResponse response) throws TemplateModelException { try { AllHttpScopesHashModel params = new AllHttpScopesHashModel(objectWrapper, servletContext, request); // Create hash model wrapper for servlet context (the application) final ServletContextHashModel servletContextModel; final TaglibFactory taglibFactory; synchronized (lazyInitFieldsLock) { if (this.servletContextModel == null) { servletContextModel = new ServletContextHashModel(this, objectWrapper); taglibFactory = createTaglibFactory(objectWrapper, servletContext); // For backward compatibility only. We don't use these: servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel); servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibFactory); initializeServletContext(request, response); this.taglibFactory = taglibFactory; this.servletContextModel = servletContextModel; } else { servletContextModel = this.servletContextModel; taglibFactory = this.taglibFactory; } } params.putUnlistedModel(KEY_APPLICATION, servletContextModel); params.putUnlistedModel(KEY_APPLICATION_PRIVATE, servletContextModel); params.putUnlistedModel(KEY_JSP_TAGLIBS, taglibFactory); // Create hash model wrapper for session HttpSessionHashModel sessionModel; HttpSession session = request.getSession(false); if (session != null) { sessionModel = (HttpSessionHashModel) session.getAttribute(ATTR_SESSION_MODEL); if (sessionModel == null || sessionModel.isOrphaned(session)) { sessionModel = new HttpSessionHashModel(session, objectWrapper); initializeSessionAndInstallModel(request, response, sessionModel, session); } } else { sessionModel = new HttpSessionHashModel(this, request, response, objectWrapper); } params.putUnlistedModel(KEY_SESSION, sessionModel); // Create hash model wrapper for request HttpRequestHashModel requestModel = (HttpRequestHashModel) request.getAttribute(ATTR_REQUEST_MODEL); if (requestModel == null || requestModel.getRequest() != request) { requestModel = new HttpRequestHashModel(request, response, objectWrapper); request.setAttribute(ATTR_REQUEST_MODEL, requestModel); request.setAttribute( ATTR_REQUEST_PARAMETERS_MODEL, createRequestParametersHashModel(request)); } params.putUnlistedModel(KEY_REQUEST, requestModel); params.putUnlistedModel(KEY_INCLUDE, new IncludePage(request, response)); params.putUnlistedModel(KEY_REQUEST_PRIVATE, requestModel); // Create hash model wrapper for request parameters HttpRequestParametersHashModel requestParametersModel = (HttpRequestParametersHashModel) request.getAttribute( ATTR_REQUEST_PARAMETERS_MODEL); params.putUnlistedModel(KEY_REQUEST_PARAMETERS, requestParametersModel); return params; } catch (ServletException e) { throw new TemplateModelException(e); } catch (IOException e) { throw new TemplateModelException(e); } } /** * Called to create the {@link TaglibFactory} once per servlet context. * The default implementation configures it based on the servlet-init parameters and various other environmental * settings, so if you override this method, you should call super, then adjust the result. * * @since 2.3.22 */ protected TaglibFactory createTaglibFactory(ObjectWrapper objectWrapper, ServletContext servletContext) throws TemplateModelException { TaglibFactory taglibFactory = new TaglibFactory(servletContext); taglibFactory.setObjectWrapper(objectWrapper); { List/*<MetaInfTldSource>*/ mergedMetaInfTldSources = new ArrayList(); if (metaInfTldSources != null) { mergedMetaInfTldSources.addAll(metaInfTldSources); } String sysPropVal = SecurityUtilities.getSystemProperty(SYSTEM_PROPERTY_META_INF_TLD_SOURCES, null); if (sysPropVal != null) { try { List metaInfTldSourcesSysProp = parseAsMetaInfTldLocations(sysPropVal); if (metaInfTldSourcesSysProp != null) { mergedMetaInfTldSources.addAll(metaInfTldSourcesSysProp); } } catch (ParseException e) { throw new TemplateModelException("Failed to parse system property \"" + SYSTEM_PROPERTY_META_INF_TLD_SOURCES + "\"", e); } } List/*<Pattern>*/ jettyTaglibJarPatterns = null; try { final String attrVal = (String) servletContext.getAttribute(ATTR_JETTY_CP_TAGLIB_JAR_PATTERNS); jettyTaglibJarPatterns = attrVal != null ? InitParamParser.parseCommaSeparatedPatterns(attrVal) : null; } catch (Exception e) { LOG.error("Failed to parse application context attribute \"" + ATTR_JETTY_CP_TAGLIB_JAR_PATTERNS + "\" - it will be ignored", e); } if (jettyTaglibJarPatterns != null) { for (Iterator/*<Pattern>*/ it = jettyTaglibJarPatterns.iterator(); it.hasNext(); ) { Pattern pattern = (Pattern) it.next(); mergedMetaInfTldSources.add(new ClasspathMetaInfTldSource(pattern)); } } taglibFactory.setMetaInfTldSources(mergedMetaInfTldSources); } { List/*<String>*/ mergedClassPathTlds = new ArrayList(); if (classpathTlds != null) { mergedClassPathTlds.addAll(classpathTlds); } String sysPropVal = SecurityUtilities.getSystemProperty(SYSTEM_PROPERTY_CLASSPATH_TLDS, null); if (sysPropVal != null) { try { List/*<String>*/ classpathTldsSysProp = InitParamParser.parseCommaSeparatedList(sysPropVal); if (classpathTldsSysProp != null) { mergedClassPathTlds.addAll(classpathTldsSysProp); } } catch (ParseException e) { throw new TemplateModelException("Failed to parse system property \"" + SYSTEM_PROPERTY_CLASSPATH_TLDS + "\"", e); } } taglibFactory.setClasspathTlds(mergedClassPathTlds); } return taglibFactory; } /** * Creates the default of the {@value #INIT_PARAM_CLASSPATH_TLDS} init-param; if this init-param is specified, it * will be appended <em>after</em> the default, not replace it. * * <p> * The implementation in {@link FreemarkerServlet} returns {@link TaglibFactory#DEFAULT_CLASSPATH_TLDS}. * * @return A {@link List} of {@link String}-s; not {@code null}. * * @since 2.3.22 */ protected List/*<MetaInfTldSource>*/ createDefaultClassPathTlds() { return TaglibFactory.DEFAULT_CLASSPATH_TLDS; } /** * Creates the default of the {@value #INIT_PARAM_META_INF_TLD_LOCATIONS} init-param; if this init-param is * specified, it will completelly <em>replace</em> the default value. * * <p> * The implementation in {@link FreemarkerServlet} returns {@link TaglibFactory#DEFAULT_META_INF_TLD_SOURCES}. * * @return A {@link List} of {@link MetaInfTldSource}-s; not {@code null}. * * @since 2.3.22 */ protected List/*<MetaInfTldSource>*/ createDefaultMetaInfTldSources() { return TaglibFactory.DEFAULT_META_INF_TLD_SOURCES; } void initializeSessionAndInstallModel(HttpServletRequest request, HttpServletResponse response, HttpSessionHashModel sessionModel, HttpSession session) throws ServletException, IOException { session.setAttribute(ATTR_SESSION_MODEL, sessionModel); initializeSession(request, response); } /** * Maps the request URL to a template path (template name) that is passed to * {@link Configuration#getTemplate(String, Locale)}. You can override it (i.e. to provide advanced rewriting * capabilities), but you are strongly encouraged to call the overridden method first, then only modify its return * value. * * @param request * The currently processed HTTP request * @return The template path (template name); can't be {@code null}. This is what's passed to * {@link Configuration#getTemplate(String)} later. (Not to be confused with the {@code templatePath} * servlet init-param of identical name; that basically specifies the "virtual file system" to which this * will be relative to.) * * @throws ServletException * Can be thrown since 2.3.22, if the template path can't be deduced from the URL. */ protected String requestUrlToTemplatePath(HttpServletRequest request) throws ServletException { // First, see if it's an included request String includeServletPath = (String) request.getAttribute("javax.servlet.include.servlet_path"); if (includeServletPath != null) { // Try path info; only if that's null (servlet is mapped to an // URL extension instead of to prefix) use servlet path. String includePathInfo = (String) request.getAttribute("javax.servlet.include.path_info"); return includePathInfo == null ? includeServletPath : includePathInfo; } // Seems that the servlet was not called as the result of a // RequestDispatcher.include(...). Try pathInfo then servletPath again, // only now directly on the request object: String path = request.getPathInfo(); if (path != null) return path; path = request.getServletPath(); if (path != null) return path; // Seems that it's a servlet mapped with prefix, and there was no extra path info. return ""; } /** * Called as the first step in request processing, before the templating mechanism * is put to work. By default does nothing and returns false. This method is * typically overridden to manage serving of non-template resources (i.e. images) * that reside in the template directory. * @param request the HTTP request * @param response the HTTP response * @return true to indicate this method has processed the request entirely, * and that the further request processing should not take place. */ protected boolean preprocessRequest( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { return false; } /** * Creates the FreeMarker {@link Configuration} singleton and (when overidden) maybe sets its defaults. Servlet * init-params will be applied later, and thus can overwrite the settings specified here. * * <p> * By overriding this method you can set your preferred {@link Configuration} setting defaults, as only the settings * for which an init-param was specified will be overwritten later. (Note that {@link FreemarkerServlet} also has * its own defaults for a few settings, but since 2.3.22, the servlet detects if those settings were already set * here and then it won't overwrite them.) * * <p> * The default implementation simply creates a new instance with {@link Configuration#Configuration()} and returns * it. */ protected Configuration createConfiguration() { // We can only set incompatible_improvements later, so ignore the deprecation warning here. return new Configuration(); } /** * Sets the defaults of the configuration that are specific to the {@link FreemarkerServlet} subclass. * This is called after the common (wired in) {@link FreemarkerServlet} setting defaults was set, also the */ protected void setConfigurationDefaults() { // do nothing } /** * Called from {@link #init()} to create the {@link ObjectWrapper}; to customzie this aspect, in most cases you * should override {@link #createDefaultObjectWrapper()} instead. Overriding this method is necessary when you want * to customize how the {@link ObjectWrapper} is created <em>from the init-param values</em>, or you want to do some * post-processing (like checking) on the created {@link ObjectWrapper}. To customize init-param interpretation, * call {@link #getInitParameter(String)} with {@link Configurable#OBJECT_WRAPPER_KEY} as argument, and see if it * returns a value that you want to interpret yourself. If was {@code null} or you don't want to interpret the * value, fall back to the super method. * * <p> * The default implementation interprets the {@code object_wrapper} servlet init-param with * calling {@link Configurable#setSetting(String, String)} (see valid values there), or if there's no such servlet * init-param, then it calls {@link #createDefaultObjectWrapper()}. * * @return The {@link ObjectWrapper} that will be used for adapting request, session, and servlet context attributes * to {@link TemplateModel}-s, and also as the object wrapper setting of {@link Configuration}. */ protected ObjectWrapper createObjectWrapper() { String wrapper = getServletConfig().getInitParameter(DEPR_INITPARAM_OBJECT_WRAPPER); if (wrapper != null) { // BC if (getInitParameter(Configurable.OBJECT_WRAPPER_KEY) != null) { throw new RuntimeException("Conflicting init-params: " + Configurable.OBJECT_WRAPPER_KEY + " and " + DEPR_INITPARAM_OBJECT_WRAPPER); } if (DEPR_INITPARAM_WRAPPER_BEANS.equals(wrapper)) { return ObjectWrapper.BEANS_WRAPPER; } if (DEPR_INITPARAM_WRAPPER_SIMPLE.equals(wrapper)) { return ObjectWrapper.SIMPLE_WRAPPER; } if (DEPR_INITPARAM_WRAPPER_JYTHON.equals(wrapper)) { // Avoiding compile-time dependency on Jython package try { return (ObjectWrapper) Class.forName("freemarker.ext.jython.JythonWrapper") .newInstance(); } catch (InstantiationException e) { throw new InstantiationError(e.getMessage()); } catch (IllegalAccessException e) { throw new IllegalAccessError(e.getMessage()); } catch (ClassNotFoundException e) { throw new NoClassDefFoundError(e.getMessage()); } } return createDefaultObjectWrapper(); } else { wrapper = getInitParameter(Configurable.OBJECT_WRAPPER_KEY); if (wrapper == null) { if (!config.isObjectWrapperExplicitlySet()) { return createDefaultObjectWrapper(); } else { return config.getObjectWrapper(); } } else { try { config.setSetting(Configurable.OBJECT_WRAPPER_KEY, wrapper); } catch (TemplateException e) { throw new RuntimeException("Failed to set " + Configurable.OBJECT_WRAPPER_KEY, e); } return config.getObjectWrapper(); } } } /** * Override this to specify what the default {@link ObjectWrapper} will be when the * {@code object_wrapper} Servlet init-param wasn't specified. Note that this is called by * {@link #createConfiguration()}, and so if that was also overidden but improperly then this method might won't be * ever called. Also note that if you set the {@code object_wrapper} in {@link #createConfiguration()}, then this * won't be called, since then that has already specified the default. * * <p> * The default implementation calls {@link Configuration#getDefaultObjectWrapper(freemarker.template.Version)}. You * should also pass in the version paramter when creating an {@link ObjectWrapper} that supports that. You can get * the version by calling {@link #getConfiguration()} and then {@link Configuration#getIncompatibleImprovements()}. * * @since 2.3.22 */ protected ObjectWrapper createDefaultObjectWrapper() { return Configuration.getDefaultObjectWrapper(config.getIncompatibleImprovements()); } /** * Should be final; don't override it. Override {@link #createObjectWrapper()} instead. */ // [2.4] Make it final protected ObjectWrapper getObjectWrapper() { return wrapper; } /** * The value of the {@code TemplatePath} init-param. {@code null} if the {@code template_loader} setting was set in * a custom {@link #createConfiguration()}. * * @deprecated Not called by FreeMarker code, and there's no point to override this (unless to cause confusion). */ @Deprecated protected final String getTemplatePath() { return templatePath; } protected HttpRequestParametersHashModel createRequestParametersHashModel(HttpServletRequest request) { return new HttpRequestParametersHashModel(request); } /** * Called when servlet detects in a request processing that * application-global (that is, ServletContext-specific) attributes are not yet * set. * This is a generic hook you might use in subclasses to perform a specific * action on first request in the context. By default it does nothing. * @param request the actual HTTP request * @param response the actual HTTP response */ protected void initializeServletContext( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } /** * Called when servlet detects in a request processing that session-global * (that is, HttpSession-specific) attributes are not yet set. * This is a generic hook you might use in subclasses to perform a specific * action on first request in the session. By default it does nothing. It * is only invoked on newly created sessions; it's not invoked when a * replicated session is reinstantiated in another servlet container. * * @param request the actual HTTP request * @param response the actual HTTP response */ protected void initializeSession( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } /** * Called before the execution is passed to template.process(). * This is a generic hook you might use in subclasses to perform a specific * action before the template is processed. By default does nothing. * A typical action to perform here is to inject application-specific * objects into the model root * * <p>Example: Expose the Serlvet context path as "baseDir" for all templates: * *<pre> * ((SimpleHash) data).put("baseDir", request.getContextPath() + "/"); * return true; *</pre> * * @param request the actual HTTP request * @param response the actual HTTP response * @param template the template that will get executed * @param data the data that will be passed to the template. By default this will be * an {@link AllHttpScopesHashModel} (which is a {@link freemarker.template.SimpleHash} subclass). * Thus, you can add new variables to the data-model with the * {@link freemarker.template.SimpleHash#put(String, Object)} subclass) method. * @return true to process the template, false to suppress template processing. */ protected boolean preTemplateProcess( HttpServletRequest request, HttpServletResponse response, Template template, TemplateModel data) throws ServletException, IOException { return true; } /** * Called after the execution returns from template.process(). * This is a generic hook you might use in subclasses to perform a specific * action after the template is processed. It will be invoked even if the * template processing throws an exception. By default does nothing. * @param request the actual HTTP request * @param response the actual HTTP response * @param template the template that was executed * @param data the data that was passed to the template */ protected void postTemplateProcess( HttpServletRequest request, HttpServletResponse response, Template template, TemplateModel data) throws ServletException, IOException { } /** * Returns the {@link freemarker.template.Configuration} object used by this servlet. * Please don't forget that {@link freemarker.template.Configuration} is not thread-safe * when you modify it. */ protected Configuration getConfiguration() { return config; } /** * If the parameter "nocache" was set to true, generate a set of headers * that will advise the HTTP client not to cache the returned page. */ private void setBrowserCachingPolicy(HttpServletResponse res) { if (noCache) { // HTTP/1.1 + IE extensions res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, " + "post-check=0, pre-check=0"); // HTTP/1.0 res.setHeader("Pragma", "no-cache"); // Last resort for those that ignore all of the above res.setHeader("Expires", EXPIRATION_DATE); } } private int parseSize(String value) throws ParseException { int lastDigitIdx; for (lastDigitIdx = value.length() - 1; lastDigitIdx >= 0; lastDigitIdx--) { char c = value.charAt(lastDigitIdx); if (c >= '0' && c <= '9') { break; } } final int n = Integer.parseInt(value.substring(0, lastDigitIdx + 1).trim()); final String unitStr = value.substring(lastDigitIdx + 1).trim().toUpperCase(); final int unit; if (unitStr.length() == 0 || unitStr.equals("B")) { unit = 1; } else if (unitStr.equals("K") || unitStr.equals("KB") || unitStr.equals("KIB")) { unit = 1024; } else if (unitStr.equals("M") || unitStr.equals("MB") || unitStr.equals("MIB")) { unit = 1024 * 1024; } else { throw new ParseException("Unknown unit: " + unitStr, lastDigitIdx + 1); } long size = (long) n * unit; if (size < 0) { throw new IllegalArgumentException("Buffer size can't be negative"); } if (size > Integer.MAX_VALUE) { throw new IllegalArgumentException("Buffer size can't bigger than " + Integer.MAX_VALUE); } return (int) size; } private static class InitParamValueException extends Exception { InitParamValueException(String initParamName, String initParamValue, Throwable casue) { super("Failed to set the " + StringUtil.jQuote(initParamName) + " servlet init-param to " + StringUtil.jQuote(initParamValue) + "; see cause exception.", casue); } public InitParamValueException(String initParamName, String initParamValue, String cause) { super("Failed to set the " + StringUtil.jQuote(initParamName) + " servlet init-param to " + StringUtil.jQuote(initParamValue) + ": " + cause); } } private static class ConflictingInitParamsException extends Exception { ConflictingInitParamsException(String recommendedName, String otherName) { super("Conflicting servlet init-params: " + StringUtil.jQuote(recommendedName) + " and " + StringUtil.jQuote(otherName) + ". Only use " + StringUtil.jQuote(recommendedName) + "."); } } private static class MalformedWebXmlException extends Exception { MalformedWebXmlException(String message) { super(message); } } }