001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.ext.servlet;
054:
055: import java.io.File;
056: import java.io.FileNotFoundException;
057: import java.io.IOException;
058: import java.text.SimpleDateFormat;
059: import java.util.Calendar;
060: import java.util.Enumeration;
061: import java.util.GregorianCalendar;
062:
063: import javax.servlet.ServletContext;
064: import javax.servlet.ServletException;
065: import javax.servlet.http.HttpServlet;
066: import javax.servlet.http.HttpServletRequest;
067: import javax.servlet.http.HttpServletResponse;
068: import javax.servlet.http.HttpSession;
069:
070: import freemarker.cache.WebappTemplateLoader;
071: import freemarker.core.Configurable;
072: import freemarker.ext.jsp.TaglibFactory;
073: import freemarker.log.Logger;
074: import freemarker.template.Configuration;
075: import freemarker.template.ObjectWrapper;
076: import freemarker.template.Template;
077: import freemarker.template.TemplateException;
078: import freemarker.template.TemplateExceptionHandler;
079: import freemarker.template.TemplateModel;
080: import freemarker.template.TemplateModelException;
081: import freemarker.template.utility.StringUtil;
082: import java.util.Locale;
083:
084: /**
085: * <p>This is a general-purpose FreeMarker view servlet.</p>
086: *
087: * <p>The main features are:
088: *
089: * <ul>
090: *
091: * <li>It makes all request, request parameters, session, and servlet
092: * context attributes available to templates through <code>Request</code>,
093: * <code>RequestParameters</code>, <code>Session</code>, and <code>Application</code>
094: * variables.
095: *
096: * <li>The scope variables are also available via automatic scope discovery. That is,
097: * writing <code>Application.attrName</code>, <code>Session.attrName</code>,
098: * <code>Request.attrName</code> is not mandatory; it's enough to write <code>attrName</code>,
099: * and if no such variable was created in the template, it will search the
100: * variable in <code>Request</code>, and then in <code>Session</code>,
101: * and finally in <code>Application</code>.
102: *
103: * <li>It creates a variable with name <code>JspTaglibs</code>, that can be used
104: * to load JSP taglibs. For example:<br>
105: * <code><#assign tiles=JspTaglibs["/WEB-INF/struts-tiles.tld"]></code>
106: *
107: * </ul>
108: *
109: * <p>The servlet will rethrow the errors occurring during template processing,
110: * wrapped into <code>ServletException</code>, except if the class name of the
111: * class set by the <code>template_exception_handler</code> contains the
112: * substring <code>"Debug"</code>. If it contains <code>"Debug"</code>, then it
113: * is assumed that the template exception handler prints the error message to the
114: * page, thus <code>FreemarkerServlet</code> will suppress the exception, and
115: * logs the problem with the servlet logger
116: * (<code>javax.servlet.GenericServlet.log(...)</code>).
117: *
118: * <p>Supported init-params are:</p>
119: *
120: * <ul>
121: *
122: * <li><strong>TemplatePath</strong> specifies the location of the templates.
123: * By default, this is interpreted as web application directory relative URI.<br>
124: * Alternatively, you can prepend it with <tt>file://</tt> to indicate a literal
125: * path in the file system (i.e. <tt>file:///var/www/project/templates/</tt>).
126: * Note that three slashes were used to specify an absolute path.<br>
127: * Also, you can prepend it with <tt>class://path/to/template/package</tt> to
128: * indicate that you want to load templates from the specified package
129: * accessible through the classloader of the servlet.<br>
130: * Default value is <tt>class://</tt> (that is, the root of the class hierarchy).
131: * <i>Note that this default is different than the default in FreeMarker 1.x, when
132: * it defaulted <tt>/</tt>, that is to loading from the webapp root directory.</i></li>
133: *
134: * <li><strong>NoCache</strong> if set to true, generates headers in the response
135: * that advise the HTTP client not to cache the returned page.
136: * The default is <tt>false</tt>.</li>
137: *
138: * <li><strong>ContentType</strong> if specified, response uses the specified
139: * Content-type HTTP header. The value may include the charset (e.g.
140: * <tt>"text/html; charset=ISO-8859-1"</tt>). If not specified, <tt>"text/html"</tt>
141: * is used. If the charset is not specified in this init-param, then the charset
142: * (encoding) of the actual template file will be used (in the response HTTP header
143: * and for encoding the output stream). Note that this setting can be overridden
144: * on a per-template basis by specifying a custom attribute named
145: * <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
146: * <tt><#ftl></tt> directive.
147: * </li>
148: *
149: * <li>The following init-params are supported only for backward compatibility, and
150: * their usage is discouraged: TemplateUpdateInterval, DefaultEncoding,
151: * ObjectWrapper, TemplateExceptionHandler. Use setting init-params such as
152: * object_wrapper instead.
153: *
154: * <li>Any other init-param will be interpreted as <code>Configuration</code>
155: * level setting. See {@link Configuration#setSetting}</li>
156: *
157: * </ul>
158: *
159: * @author Attila Szegedi
160: * @version $Id: FreemarkerServlet.java,v 1.82.2.5 2006/06/21 13:02:01 ddekany Exp $
161: */
162:
163: public class FreemarkerServlet extends HttpServlet {
164: private static final Logger logger = Logger
165: .getLogger("freemarker.servlet");
166:
167: public static final long serialVersionUID = -2440216393145762479L;
168:
169: private static final String INITPARAM_TEMPLATE_PATH = "TemplatePath";
170: private static final String INITPARAM_NOCACHE = "NoCache";
171: private static final String INITPARAM_CONTENT_TYPE = "ContentType";
172: private static final String DEFAULT_CONTENT_TYPE = "text/html";
173: private static final String INITPARAM_DEBUG = "Debug";
174:
175: private static final String DEPR_INITPARAM_TEMPLATE_DELAY = "TemplateDelay";
176: private static final String DEPR_INITPARAM_ENCODING = "DefaultEncoding";
177: private static final String DEPR_INITPARAM_OBJECT_WRAPPER = "ObjectWrapper";
178: private static final String DEPR_INITPARAM_WRAPPER_SIMPLE = "simple";
179: private static final String DEPR_INITPARAM_WRAPPER_BEANS = "beans";
180: private static final String DEPR_INITPARAM_WRAPPER_JYTHON = "jython";
181: private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER = "TemplateExceptionHandler";
182: private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW = "rethrow";
183: private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG = "debug";
184: private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_HTML_DEBUG = "htmlDebug";
185: private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE = "ignore";
186: private static final String DEPR_INITPARAM_DEBUG = "debug";
187:
188: public static final String KEY_REQUEST = "Request";
189: public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
190: public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
191: public static final String KEY_SESSION = "Session";
192: public static final String KEY_APPLICATION = "Application";
193: public static final String KEY_APPLICATION_PRIVATE = "__FreeMarkerServlet.Application__";
194: public static final String KEY_JSP_TAGLIBS = "JspTaglibs";
195:
196: // Note these names start with dot, so they're essentially invisible from
197: // a freemarker script.
198: private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
199: private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters";
200: private static final String ATTR_SESSION_MODEL = ".freemarker.Session";
201: private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application";
202: private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs";
203:
204: private static final String EXPIRATION_DATE;
205:
206: static {
207: // Generate expiration date that is one year from now in the past
208: GregorianCalendar expiration = new GregorianCalendar();
209: expiration.roll(Calendar.YEAR, -1);
210: SimpleDateFormat httpDate = new SimpleDateFormat(
211: "EEE, dd MMM yyyy HH:mm:ss z", java.util.Locale.US);
212: EXPIRATION_DATE = httpDate.format(expiration.getTime());
213: }
214:
215: private String templatePath;
216: private boolean nocache;
217: protected boolean debug;
218: private Configuration config;
219: private ObjectWrapper wrapper;
220: private String contentType;
221: private boolean noCharsetInContentType;
222:
223: public void init() throws ServletException {
224: try {
225: config = createConfiguration();
226:
227: // Set defaults:
228: config
229: .setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
230: contentType = DEFAULT_CONTENT_TYPE;
231:
232: // Process object_wrapper init-param out of order:
233: wrapper = createObjectWrapper();
234: if (logger.isDebugEnabled()) {
235: logger.debug("Using object wrapper of class "
236: + wrapper.getClass().getName());
237: }
238: config.setObjectWrapper(wrapper);
239:
240: // Process TemplatePath init-param out of order:
241: templatePath = getInitParameter(INITPARAM_TEMPLATE_PATH);
242: if (templatePath == null)
243: templatePath = "class://";
244: if (templatePath.startsWith("class://")) {
245: // substring(7) is intentional as we "reuse" the last slash
246: config.setClassForTemplateLoading(getClass(),
247: templatePath.substring(7));
248: } else {
249: if (templatePath.startsWith("file://")) {
250: templatePath = templatePath.substring(7);
251: config.setDirectoryForTemplateLoading(new File(
252: templatePath));
253: } else {
254: config.setTemplateLoader(new WebappTemplateLoader(
255: this .getServletContext(), templatePath));
256: }
257: }
258:
259: // Process all other init-params:
260: Enumeration initpnames = getServletConfig()
261: .getInitParameterNames();
262: while (initpnames.hasMoreElements()) {
263: String name = (String) initpnames.nextElement();
264: String value = getInitParameter(name);
265:
266: if (name == null) {
267: throw new ServletException(
268: "init-param without param-name. "
269: + "Maybe the web.xml is not well-formed?");
270: }
271: if (value == null) {
272: throw new ServletException(
273: "init-param without param-value. "
274: + "Maybe the web.xml is not well-formed?");
275: }
276:
277: if (name.equals(DEPR_INITPARAM_OBJECT_WRAPPER)
278: || name.equals(Configurable.OBJECT_WRAPPER_KEY)
279: || name.equals(INITPARAM_TEMPLATE_PATH)) {
280: // ignore: we have already processed these
281: } else if (name.equals(DEPR_INITPARAM_ENCODING)) { // BC
282: if (getInitParameter(Configuration.DEFAULT_ENCODING_KEY) != null) {
283: throw new ServletException(
284: "Conflicting init-params: "
285: + Configuration.DEFAULT_ENCODING_KEY
286: + " and "
287: + DEPR_INITPARAM_ENCODING);
288: }
289: config.setDefaultEncoding(value);
290: } else if (name.equals(DEPR_INITPARAM_TEMPLATE_DELAY)) { // BC
291: if (getInitParameter(Configuration.TEMPLATE_UPDATE_DELAY_KEY) != null) {
292: throw new ServletException(
293: "Conflicting init-params: "
294: + Configuration.TEMPLATE_UPDATE_DELAY_KEY
295: + " and "
296: + DEPR_INITPARAM_TEMPLATE_DELAY);
297: }
298: try {
299: config.setTemplateUpdateDelay(Integer
300: .parseInt(value));
301: } catch (NumberFormatException e) {
302: // Intentionally ignored
303: }
304: } else if (name
305: .equals(DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER)) { // BC
306: if (getInitParameter(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY) != null) {
307: throw new ServletException(
308: "Conflicting init-params: "
309: + Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY
310: + " and "
311: + DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER);
312: }
313:
314: if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW
315: .equals(value)) {
316: config
317: .setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
318: } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG
319: .equals(value)) {
320: config
321: .setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
322: } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_HTML_DEBUG
323: .equals(value)) {
324: config
325: .setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
326: } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE
327: .equals(value)) {
328: config
329: .setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
330: } else {
331: throw new ServletException(
332: "Invalid value for servlet init-param "
333: + DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER
334: + ": " + value);
335: }
336: } else if (name.equals(INITPARAM_NOCACHE)) {
337: nocache = StringUtil.getYesNo(value);
338: } else if (name.equals(DEPR_INITPARAM_DEBUG)) { // BC
339: if (getInitParameter(INITPARAM_DEBUG) != null) {
340: throw new ServletException(
341: "Conflicting init-params: "
342: + INITPARAM_DEBUG + " and "
343: + DEPR_INITPARAM_DEBUG);
344: }
345: debug = StringUtil.getYesNo(value);
346: } else if (name.equals(INITPARAM_DEBUG)) {
347: debug = StringUtil.getYesNo(value);
348: } else if (name.equals(INITPARAM_CONTENT_TYPE)) {
349: contentType = value;
350: } else {
351: config.setSetting(name, value);
352: }
353: } // while initpnames
354:
355: noCharsetInContentType = true;
356: int i = contentType.toLowerCase().indexOf("charset=");
357: if (i != -1) {
358: char c = ' ';
359: i--;
360: while (i >= 0) {
361: c = contentType.charAt(i);
362: if (!Character.isWhitespace(c))
363: break;
364: i--;
365: }
366: if (i == -1 || c == ';') {
367: noCharsetInContentType = false;
368: }
369: }
370: } catch (ServletException e) {
371: throw e;
372: } catch (Exception e) {
373: throw new ServletException(e);
374: }
375: }
376:
377: public void doGet(HttpServletRequest request,
378: HttpServletResponse response) throws ServletException,
379: IOException {
380: process(request, response);
381: }
382:
383: public void doPost(HttpServletRequest request,
384: HttpServletResponse response) throws ServletException,
385: IOException {
386: process(request, response);
387: }
388:
389: private void process(HttpServletRequest request,
390: HttpServletResponse response) throws ServletException,
391: IOException {
392: // Give chance to subclasses to perform preprocessing
393: if (preprocessRequest(request, response)) {
394: return;
395: }
396:
397: String path = requestUrlToTemplatePath(request);
398:
399: if (debug) {
400: log("Requested template: " + path);
401: }
402:
403: Template template = null;
404: try {
405: template = config.getTemplate(path, deduceLocale(path,
406: request, response));
407: } catch (FileNotFoundException e) {
408: response.sendError(HttpServletResponse.SC_NOT_FOUND);
409: return;
410: }
411:
412: Object attrContentType = template
413: .getCustomAttribute("content_type");
414: if (attrContentType != null) {
415: response.setContentType(attrContentType.toString());
416: } else {
417: if (noCharsetInContentType) {
418: response.setContentType(contentType + "; charset="
419: + template.getEncoding());
420: } else {
421: response.setContentType(contentType);
422: }
423: }
424:
425: // Set cache policy
426: setBrowserCachingPolicy(response);
427:
428: ServletContext servletContext = getServletContext();
429: try {
430: TemplateModel model = createModel(wrapper, servletContext,
431: request, response);
432:
433: // Give subclasses a chance to hook into preprocessing
434: if (preTemplateProcess(request, response, template, model)) {
435: try {
436: // Process the template
437: template.process(model, response.getWriter());
438: } finally {
439: // Give subclasses a chance to hook into postprocessing
440: postTemplateProcess(request, response, template,
441: model);
442: }
443: }
444: } catch (TemplateException te) {
445: if (config.getTemplateExceptionHandler().getClass()
446: .getName().indexOf("Debug") != -1) {
447: this .log("Error executing FreeMarker template", te);
448: } else {
449: ServletException e = new ServletException(
450: "Error executing FreeMarker template", te);
451: // Attempt to set init cause, but don't freak out if the method
452: // is not available (i.e. pre-1.4 JRE). This is required as the
453: // constructor-passed throwable won't show up automatically in
454: // stack traces.
455: try {
456: e.getClass().getMethod("initCause",
457: new Class[] { Throwable.class }).invoke(e,
458: new Object[] { te });
459: } catch (Exception ex) {
460: // Can't set init cause, we're probably running on a pre-1.4
461: // JDK, oh well...
462: }
463: throw e;
464: }
465: }
466: }
467:
468: /**
469: * Returns the locale used for the
470: * {@link Configuration#getTemplate(String, Locale)} call.
471: * The base implementation simply returns the locale setting of the
472: * configuration. Override this method to provide different behaviour, i.e.
473: * to use the locale indicated in the request.
474: */
475: protected Locale deduceLocale(String templatePath,
476: HttpServletRequest request, HttpServletResponse response) {
477: return config.getLocale();
478: }
479:
480: protected TemplateModel createModel(ObjectWrapper wrapper,
481: ServletContext servletContext, HttpServletRequest request,
482: HttpServletResponse response) throws TemplateModelException {
483: try {
484: AllHttpScopesHashModel params = new AllHttpScopesHashModel(
485: wrapper, servletContext, request);
486:
487: // Create hash model wrapper for servlet context (the application)
488: ServletContextHashModel servletContextModel = (ServletContextHashModel) servletContext
489: .getAttribute(ATTR_APPLICATION_MODEL);
490: if (servletContextModel == null) {
491: servletContextModel = new ServletContextHashModel(this ,
492: wrapper);
493: servletContext.setAttribute(ATTR_APPLICATION_MODEL,
494: servletContextModel);
495: TaglibFactory taglibs = new TaglibFactory(
496: servletContext);
497: servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL,
498: taglibs);
499: initializeServletContext(request, response);
500: }
501: params.putUnlistedModel(KEY_APPLICATION,
502: servletContextModel);
503: params.putUnlistedModel(KEY_APPLICATION_PRIVATE,
504: servletContextModel);
505: params.putUnlistedModel(KEY_JSP_TAGLIBS,
506: (TemplateModel) servletContext
507: .getAttribute(ATTR_JSP_TAGLIBS_MODEL));
508: // Create hash model wrapper for session
509: HttpSessionHashModel sessionModel;
510: HttpSession session = request.getSession(false);
511: if (session != null) {
512: sessionModel = (HttpSessionHashModel) session
513: .getAttribute(ATTR_SESSION_MODEL);
514: if (sessionModel == null || sessionModel.isZombie()) {
515: sessionModel = new HttpSessionHashModel(session,
516: wrapper);
517: session.setAttribute(ATTR_SESSION_MODEL,
518: sessionModel);
519: if (!sessionModel.isZombie()) {
520: initializeSession(request, response);
521: }
522: }
523: } else {
524: sessionModel = new HttpSessionHashModel(this , request,
525: response, wrapper);
526: }
527: params.putUnlistedModel(KEY_SESSION, sessionModel);
528:
529: // Create hash model wrapper for request
530: HttpRequestHashModel requestModel = (HttpRequestHashModel) request
531: .getAttribute(ATTR_REQUEST_MODEL);
532: if (requestModel == null
533: || requestModel.getRequest() != request) {
534: requestModel = new HttpRequestHashModel(request,
535: response, wrapper);
536: request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
537: request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL,
538: createRequestParametersHashModel(request));
539: }
540: params.putUnlistedModel(KEY_REQUEST, requestModel);
541: params.putUnlistedModel(KEY_REQUEST_PRIVATE, requestModel);
542:
543: // Create hash model wrapper for request parameters
544: HttpRequestParametersHashModel requestParametersModel = (HttpRequestParametersHashModel) request
545: .getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
546: params.putUnlistedModel(KEY_REQUEST_PARAMETERS,
547: requestParametersModel);
548: return params;
549: } catch (ServletException e) {
550: throw new TemplateModelException(e);
551: } catch (IOException e) {
552: throw new TemplateModelException(e);
553: }
554: }
555:
556: /**
557: * Maps the request URL to a template path that is passed to
558: * {@link Configuration#getTemplate(String, Locale)}. You can override it
559: * (i.e. to provide advanced rewriting capabilities), but you are strongly
560: * encouraged to call the overridden method first, then only modify its
561: * return value.
562: * @param request the currently processed request
563: * @return a String representing the template path
564: */
565: protected String requestUrlToTemplatePath(HttpServletRequest request) {
566: // First, see if it is an included request
567: String includeServletPath = (String) request
568: .getAttribute("javax.servlet.include.servlet_path");
569: if (includeServletPath != null) {
570: // Try path info; only if that's null (servlet is mapped to an
571: // URL extension instead of to prefix) use servlet path.
572: String includePathInfo = (String) request
573: .getAttribute("javax.servlet.include.path_info");
574: return includePathInfo == null ? includeServletPath
575: : includePathInfo;
576: }
577: // Seems that the servlet was not called as the result of a
578: // RequestDispatcher.include(...). Try pathInfo then servletPath again,
579: // only now directly on the request object:
580: String path = request.getPathInfo();
581: if (path != null)
582: return path;
583: path = request.getServletPath();
584: if (path != null)
585: return path;
586: // Seems that it is a servlet mapped with prefix, and there was no extra path info.
587: return "";
588: }
589:
590: /**
591: * Called as the first step in request processing, before the templating mechanism
592: * is put to work. By default does nothing and returns false. This method is
593: * typically overridden to manage serving of non-template resources (i.e. images)
594: * that reside in the template directory.
595: * @param request the HTTP request
596: * @param response the HTTP response
597: * @return true to indicate this method has processed the request entirely,
598: * and that the further request processing should not take place.
599: */
600: protected boolean preprocessRequest(HttpServletRequest request,
601: HttpServletResponse response) throws ServletException,
602: IOException {
603: return false;
604: }
605:
606: /**
607: * This method is called from {@link #init()} to create the
608: * FreeMarker configuration object that this servlet will use
609: * for template loading. This is a hook that allows you
610: * to custom-configure the configuration object in a subclass.
611: * The default implementation returns a new {@link Configuration}
612: * instance.
613: */
614: protected Configuration createConfiguration() {
615: return new Configuration();
616: }
617:
618: /**
619: * This method is called from {@link #init()} to create the
620: * FreeMarker object wrapper object that this servlet will use
621: * for adapting request, session, and servlet context attributes into
622: * template models.. This is a hook that allows you
623: * to custom-configure the wrapper object in a subclass.
624: * The default implementation returns a wrapper that depends on the value
625: * of <code>ObjectWrapper</code> init parameter. If <code>simple</code> is
626: * specified, {@link ObjectWrapper#SIMPLE_WRAPPER} is used; if <code>jython</code>
627: * is specified, {@link freemarker.ext.jython.JythonWrapper} is used. In
628: * every other case {@link ObjectWrapper#DEFAULT_WRAPPER} is used.
629: */
630: protected ObjectWrapper createObjectWrapper() {
631: String wrapper = getServletConfig().getInitParameter(
632: DEPR_INITPARAM_OBJECT_WRAPPER);
633: if (wrapper != null) { // BC
634: if (getInitParameter(Configurable.OBJECT_WRAPPER_KEY) != null) {
635: throw new RuntimeException("Conflicting init-params: "
636: + Configurable.OBJECT_WRAPPER_KEY + " and "
637: + DEPR_INITPARAM_OBJECT_WRAPPER);
638: }
639: if (DEPR_INITPARAM_WRAPPER_BEANS.equals(wrapper)) {
640: return ObjectWrapper.BEANS_WRAPPER;
641: }
642: if (DEPR_INITPARAM_WRAPPER_SIMPLE.equals(wrapper)) {
643: return ObjectWrapper.SIMPLE_WRAPPER;
644: }
645: if (DEPR_INITPARAM_WRAPPER_JYTHON.equals(wrapper)) {
646: // Avoiding compile-time dependency on Jython package
647: try {
648: return (ObjectWrapper) Class.forName(
649: "freemarker.ext.jython.JythonWrapper")
650: .newInstance();
651: } catch (InstantiationException e) {
652: throw new InstantiationError(e.getMessage());
653: } catch (IllegalAccessException e) {
654: throw new IllegalAccessError(e.getMessage());
655: } catch (ClassNotFoundException e) {
656: throw new NoClassDefFoundError(e.getMessage());
657: }
658: }
659: // return BeansWrapper.getDefaultInstance();
660: return ObjectWrapper.DEFAULT_WRAPPER;
661: } else {
662: wrapper = getInitParameter(Configurable.OBJECT_WRAPPER_KEY);
663: if (wrapper == null) {
664: // return BeansWrapper.getDefaultInstance();
665: return ObjectWrapper.DEFAULT_WRAPPER;
666: } else {
667: try {
668: config.setSetting(Configurable.OBJECT_WRAPPER_KEY,
669: wrapper);
670: } catch (TemplateException e) {
671: throw new RuntimeException(e.toString());
672: }
673: return config.getObjectWrapper();
674: }
675: }
676: }
677:
678: protected ObjectWrapper getObjectWrapper() {
679: return wrapper;
680: }
681:
682: protected final String getTemplatePath() {
683: return templatePath;
684: }
685:
686: protected HttpRequestParametersHashModel createRequestParametersHashModel(
687: HttpServletRequest request) {
688: return new HttpRequestParametersHashModel(request);
689: }
690:
691: /**
692: * Called when servlet detects in a request processing that
693: * application-global (that is, ServletContext-specific) attributes are not yet
694: * set.
695: * This is a generic hook you might use in subclasses to perform a specific
696: * action on first request in the context. By default it does nothing.
697: * @param request the actual HTTP request
698: * @param response the actual HTTP response
699: */
700: protected void initializeServletContext(HttpServletRequest request,
701: HttpServletResponse response) throws ServletException,
702: IOException {
703: }
704:
705: /**
706: * Called when servlet detects in a request processing that session-global
707: * (that is, HttpSession-specific) attributes are not yet set.
708: * This is a generic hook you might use in subclasses to perform a specific
709: * action on first request in the session. By default it does nothing. It
710: * is only invoked on newly created sessions; it is not invoked when a
711: * replicated session is reinstantiated in another servlet container.
712: *
713: * @param request the actual HTTP request
714: * @param response the actual HTTP response
715: */
716: protected void initializeSession(HttpServletRequest request,
717: HttpServletResponse response) throws ServletException,
718: IOException {
719: }
720:
721: /**
722: * Called before the execution is passed to template.process().
723: * This is a generic hook you might use in subclasses to perform a specific
724: * action before the template is processed. By default does nothing.
725: * A typical action to perform here is to inject application-specific
726: * objects into the model root
727: * @param request the actual HTTP request
728: * @param response the actual HTTP response
729: * @param template the template that will get executed
730: * @param data the data that will be passed to the template
731: * @return true to process the template, false to suppress template processing.
732: */
733: protected boolean preTemplateProcess(HttpServletRequest request,
734: HttpServletResponse response, Template template,
735: TemplateModel data) throws ServletException, IOException {
736: return true;
737: }
738:
739: /**
740: * Called after the execution returns from template.process().
741: * This is a generic hook you might use in subclasses to perform a specific
742: * action after the template is processed. It will be invoked even if the
743: * template processing throws an exception. By default does nothing.
744: * @param request the actual HTTP request
745: * @param response the actual HTTP response
746: * @param template the template that was executed
747: * @param data the data that was passed to the template
748: */
749: protected void postTemplateProcess(HttpServletRequest request,
750: HttpServletResponse response, Template template,
751: TemplateModel data) throws ServletException, IOException {
752: }
753:
754: /**
755: * Returns the {@link freemarker.template.Configuration} object used by this servlet.
756: * Please don't forget that {@link freemarker.template.Configuration} is not thread-safe
757: * when you modify it.
758: */
759: protected Configuration getConfiguration() {
760: return config;
761: }
762:
763: /**
764: * If the parameter "nocache" was set to true, generate a set of headers
765: * that will advise the HTTP client not to cache the returned page.
766: */
767: private void setBrowserCachingPolicy(HttpServletResponse res) {
768: if (nocache) {
769: // HTTP/1.1 + IE extensions
770: res.setHeader("Cache-Control",
771: "no-store, no-cache, must-revalidate, "
772: + "post-check=0, pre-check=0");
773: // HTTP/1.0
774: res.setHeader("Pragma", "no-cache");
775: // Last resort for those that ignore all of the above
776: res.setHeader("Expires", EXPIRATION_DATE);
777: }
778: }
779: }
|