View Javadoc

    1   /*
    2    * $Id: TemplateServlet.java,v 1.9 2005/01/10 15:42:20 glaforge Exp $
    3    *
    4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5    *
    6    * Redistribution and use of this software and associated documentation
    7    * ("Software"), with or without modification, are permitted provided that the
    8    * following conditions are met:
    9    *
    10   * 1. Redistributions of source code must retain copyright statements and
    11   * notices. Redistributions must also contain a copy of this document.
    12   *
    13   * 2. Redistributions in binary form must reproduce the above copyright notice,
    14   * this list of conditions and the following disclaimer in the documentation
    15   * and/or other materials provided with the distribution.
    16   *
    17   * 3. The name "groovy" must not be used to endorse or promote products derived
    18   * from this Software without prior written permission of The Codehaus. For
    19   * written permission, please contact info@codehaus.org.
    20   *
    21   * 4. Products derived from this Software may not be called "groovy" nor may
    22   * "groovy" appear in their names without prior written permission of The
    23   * Codehaus. "groovy" is a registered trademark of The Codehaus.
    24   *
    25   * 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
    26   *
    27   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
    28   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    29   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    30   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
    31   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    32   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    33   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    34   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    35   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    36   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    37   *
    38   */
    39  package groovy.servlet;
    40
    41  import groovy.lang.Binding;
    42  import groovy.text.SimpleTemplateEngine;
    43  import groovy.text.Template;
    44  import groovy.text.TemplateEngine;
    45
    46  import java.io.FileNotFoundException;
    47  import java.io.IOException;
    48  import java.io.Writer;
    49  import java.net.URL;
    50
    51  import javax.servlet.ServletConfig;
    52  import javax.servlet.ServletContext;
    53  import javax.servlet.ServletException;
    54  import javax.servlet.http.HttpServlet;
    55  import javax.servlet.http.HttpServletRequest;
    56  import javax.servlet.http.HttpServletResponse;
    57
    58  /***
    59   * A generic servlet for templates.
    60   *
    61   * It wraps a <code>groovy.text.TemplateEngine</code> to process HTTP
    62   * requests. By default, it uses the
    63   * <code>groovy.text.SimpleTemplateEngine</code> which interprets JSP-like (or
    64   * Canvas-like) templates. <br>
    65   * <br>
    66   *
    67   * Example <code>HelloWorld.template</code>:
    68   *
    69   * <pre><code>
    70   *
    71   *  &lt;html&gt;
    72   *  &lt;body&gt;
    73   *  &lt;% 3.times { %&gt;
    74   *  Hello World!
    75   * <br>
    76   *
    77   *  &lt;% } %&gt;
    78   *  &lt;/body&gt;
    79   *  &lt;/html&gt;
    80   *
    81   * </code></pre>
    82   *
    83   * <br>
    84   * <br>
    85   *
    86   * Note: <br>
    87   * Automatic binding of context variables and request (form) parameters is
    88   * disabled by default. You can enable it by setting the servlet config init
    89   * parameters to <code>true</code>.
    90   *
    91   * <pre><code>
    92   * bindDefaultVariables = init(&quot;bindDefaultVariables&quot;, false);
    93   * bindRequestParameters = init(&quot;bindRequestParameters&quot;, false);
    94   * </code></pre>
    95   *
    96   * @author <a mailto:sormuras@web.de>Christian Stein </a>
    97   * @author Guillaume Laforge
    98   * @version 1.3
    99   */
    100 public class TemplateServlet extends HttpServlet {
    101
    102     public static final String DEFAULT_CONTENT_TYPE = "text/html";
    103
    104     private ServletContext servletContext;
    105
    106     protected TemplateEngine templateEngine;
    107
    108     /***
    109      * Initializes the servlet.
    110      *
    111      * @param config
    112      *            Passed by the servlet container.
    113      */
    114     public void init(ServletConfig config) {
    115
    116         /*
    117          * Save the context.
    118          */
    119         this.servletContext = config.getServletContext();
    120
    121         /*
    122          * BEGIN
    123          */
    124         String className = getClass().getName();
    125         servletContext.log("Initializing on " + className + "...");
    126
    127         /*
    128          * Get TemplateEngine instance.
    129          */
    130         this.templateEngine = createTemplateEngine(config);
    131         if (templateEngine == null) { throw new RuntimeException("Template engine not instantiated."); }
    132
    133         /*
    134          * END;
    135          */
    136         String engineName = templateEngine.getClass().getName();
    137         servletContext.log(className + " initialized on " + engineName + ".");
    138     }
    139
    140     /***
    141      * Convient evaluation of boolean configuration parameters.
    142      *
    143      * @return <code>true</code> or <code>false</code>.
    144      * @param config
    145      *            Servlet configuration passed by the servlet container.
    146      * @param param
    147      *            Name of the paramter to look up.
    148      * @param value
    149      *            Default value if parameter name is not set.
    150      */
    151     protected boolean init(ServletConfig config, String param, boolean value) {
    152         String string = config.getInitParameter(param);
    153         if (string == null) { return value; }
    154         return Boolean.valueOf(string).booleanValue();
    155     }
    156
    157     /***
    158      * Creates the template engine.
    159      *
    160      * Called by {@link #init(ServletConfig)} and returns just <code>
    161      * SimpleTemplateEngine()</code> if the init parameter <code>templateEngine</code>
    162      * is not set.
    163      *
    164      * @return The underlying template engine.
    165      * @param config
    166      *            This serlvet configuration passed by the container.
    167      * @see #createTemplateEngine(javax.servlet.ServletConfig)
    168      */
    169     protected TemplateEngine createTemplateEngine(ServletConfig config) {
    170         String templateEngineClassName = config.getInitParameter("templateEngine");
    171         if (templateEngineClassName == null) {
    172             return new SimpleTemplateEngine();
    173         }
    174         try {
    175             return (TemplateEngine) Class.forName(templateEngineClassName).newInstance();
    176         } catch (InstantiationException e) {
    177             servletContext.log("Could not instantiate template engine: " + templateEngineClassName, e);
    178         } catch (IllegalAccessException e) {
    179             servletContext.log("Could not access template engine class: " + templateEngineClassName, e);
    180         } catch (ClassNotFoundException e) {
    181             servletContext.log("Could not find template engine class: " + templateEngineClassName, e);
    182        }
    183         return null;
    184     }
    185
    186     /***
    187      * Delegates to {@link #doRequest(HttpServletRequest, HttpServletResponse)}.
    188      */
    189     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    190         doRequest(request, response);
    191     }
    192
    193     /***
    194      * Delegates to {@link #doRequest(HttpServletRequest, HttpServletResponse)}.
    195      */
    196     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    197         doRequest(request, response);
    198     }
    199
    200     /***
    201      * Processes all requests by dispatching to helper methods.
    202      *
    203      * TODO Outline the algorithm. Although the method names are well-chosen. :)
    204      *
    205      * @param request
    206      *            The http request.
    207      * @param response
    208      *            The http response.
    209      * @throws ServletException
    210      *             ...
    211      */
    212     protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
    213
    214         Binding binding = null;
    215
    216         try {
    217
    218             /*
    219              * Create binding.
    220              */
    221             binding = new ServletBinding(request, response, servletContext);
    222
    223             /*
    224              * Set default content type.
    225              */
    226             setContentType(request, response);
    227
    228             /*
    229              * Create the template by its engine.
    230              */
    231             Template template = handleRequest(request, response, binding);
    232
    233             /*
    234              * Let the template, that is groovy, do the merge.
    235              */
    236             merge(template, binding, response);
    237
    238         }
    239         catch (Exception exception) {
    240
    241             /*
    242              * Call exception handling hook.
    243              */
    244             error(request, response, exception);
    245
    246         }
    247         finally {
    248
    249             /*
    250              * Indicate we are finally done with this request.
    251              */
    252             requestDone(request, response, binding);
    253
    254         }
    255
    256     }
    257
    258     /***
    259      * Sets {@link #DEFAULT_CONTENT_TYPE}.
    260      *
    261      * @param request
    262      *            The HTTP request.
    263      * @param response
    264      *            The HTTP response.
    265      */
    266     protected void setContentType(HttpServletRequest request, HttpServletResponse response) {
    267
    268         response.setContentType(DEFAULT_CONTENT_TYPE);
    269
    270     }
    271
    272     /***
    273      * Default request handling. <br>
    274      *
    275      * Leaving Velocity behind again. The template, actually the Groovy code in
    276      * it, could handle/process the entire request. Good or not? This depends on
    277      * you! :)<br>
    278      *
    279      * Anyway, here no exception is thrown -- but it's strongly recommended to
    280      * override this method in derived class and do the real processing against
    281      * the model inside it. The template should be used, like Velocity
    282      * templates, to produce the view, the html page. Again, it's up to you!
    283      *
    284      * @return The template that will be merged.
    285      * @param request
    286      *            The HTTP request.
    287      * @param response
    288      *            The HTTP response.
    289      * @param binding
    290      *            The application context.
    291      * @throws Exception
    292      */
    293     protected Template handleRequest(
    294             HttpServletRequest request,
    295             HttpServletResponse response,
    296             Binding binding)
    297     throws Exception {
    298         /*
    299          * Delegate to getTemplate(String).
    300          */
    301         return getTemplate(request);
    302     }
    303
    304     /***
    305      * Gets the template by its name.
    306      *
    307      * @return The template that will be merged.
    308      * @param request
    309      *            The HttpServletRequest.
    310      * @throws Exception
    311      *             Any exception.
    312      */
    313     protected Template getTemplate(HttpServletRequest request) throws Exception {
    314
    315         /*
    316          * If its an include we need to get the included path, not the main request path.
    317          */
    318         String path = (String) request.getAttribute("javax.servlet.include.servlet_path");
    319         if (path == null) {
    320             path = request.getServletPath();
    321         }
    322
    323         /*
    324          * Delegate to resolveTemplateName(String). Twice if necessary.
    325          */
    326         URL url = resolveTemplateName(path);
    327         if (url == null) {
    328             url = resolveTemplateName(request.getRequestURI());
    329         }
    330
    331         /*
    332          * Template not found?
    333          */
    334         if (url == null) {
    335             String uri = request.getRequestURI();
    336             servletContext.log("Resource \"" + uri + "\" not found.");
    337             throw new FileNotFoundException(uri);
    338         }
    339
    340         /*
    341          * Delegate to getTemplate(URL).
    342          */
    343         return getTemplate(url);
    344
    345     }
    346
    347     /***
    348      * Locate template and convert its location to an URL.
    349      *
    350      * @return The URL pointing to the resource... the template.
    351      * @param templateName
    352      *            The name of the template.
    353      * @throws Exception
    354      *             Any exception.
    355      */
    356     protected URL resolveTemplateName(String templateName) throws Exception {
    357
    358         /*
    359          * Try servlet context resource facility.
    360          *
    361          * Good for names pointing to templates relatively to the servlet
    362          * context.
    363          */
    364         URL url = servletContext.getResource(templateName);
    365         if (url != null) { return url; }
    366
    367         /*
    368          * Precedence: Context classloader, Class classloader
    369          * (those classloaders will delegate to the system classloader)
    370          */
    371         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    372         url = classLoader.getResource(templateName);
    373         if (url != null) { return url; }
    374
    375         /*
    376          * Try the class loader, that loaded this class.
    377          *
    378          * Good for templates located within the class path.
    379          *
    380          */
    381         url = getClass().getResource(templateName);
    382         if (url != null) { return url; }
    383
    384         /*
    385          * Still, still here? Just return null.
    386          */
    387         return null;
    388
    389     }
    390
    391     /***
    392      * Gets the template by its url.
    393      *
    394      * @return The template that will be merged.
    395      * @param templateURL
    396      *            The url of the template.
    397      * @throws Exception
    398      *             Any exception.
    399      */
    400     protected Template getTemplate(URL templateURL) throws Exception {
    401
    402         /*
    403          * Let the engine create the template from given URL.
    404          *
    405          * TODO Is createTemplate(Reader); faster? Fail safer?
    406          */
    407         return templateEngine.createTemplate(templateURL);
    408
    409     }
    410
    411     /***
    412      * Merges the template and writes response.
    413      *
    414      * @param template
    415      *            The template that will be merged... now!
    416      * @param binding
    417      *            The application context.
    418      * @param response
    419      *            The HTTP response.
    420      * @throws Exception
    421      *             Any exception.
    422      */
    423     protected void merge(Template template, Binding binding, HttpServletResponse response) throws Exception {
    424
    425         /*
    426          * Set binding and write response.
    427          */
    428         template.make(binding.getVariables()).writeTo((Writer) binding.getVariable("out"));
    429
    430     }
    431
    432     /***
    433      * Simply sends an internal server error page (code 500).
    434      *
    435      * @param request
    436      *            The HTTP request.
    437      * @param response
    438      *            The HTTP response.
    439      * @param exception
    440      *            The cause.
    441      */
    442     protected void error(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    443
    444         try {
    445             response.sendError(500, exception.getMessage());
    446         }
    447         catch (IOException ioException) {
    448             servletContext.log("Should not happen.", ioException);
    449         }
    450
    451     }
    452
    453     /***
    454      * Called one request is processed.
    455      *
    456      * This clean-up hook is always called, even if there was an exception
    457      * flying around and the error method was executed.
    458      *
    459      * @param request
    460      *            The HTTP request.
    461      * @param response
    462      *            The HTTP response.
    463      * @param binding
    464      *            The application context.
    465      */
    466     protected void requestDone(HttpServletRequest request, HttpServletResponse response, Binding binding) {
    467
    468         /*
    469          * Nothing to clean up.
    470          */
    471         return;
    472
    473     }
    474
    475 }