View Javadoc

    1   /*
    2    * $Id: AbstractHttpServlet.java,v 1.9 2006/01/23 10:36:22 cstein 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.MetaClass;
    42  import groovy.util.ResourceConnector;
    43  import groovy.util.ResourceException;
    44
    45  import java.io.File;
    46  import java.io.IOException;
    47  import java.net.URL;
    48  import java.net.URLConnection;
    49  import java.util.regex.Matcher;
    50  import java.util.regex.Pattern;
    51
    52  import javax.servlet.ServletConfig;
    53  import javax.servlet.ServletContext;
    54  import javax.servlet.ServletException;
    55  import javax.servlet.http.HttpServlet;
    56  import javax.servlet.http.HttpServletRequest;
    57
    58  /***
    59   * A common ground dealing with the HTTP servlet API wrinkles.
    60   *
    61   * <h4>Resource name mangling (pattern replacement)</h4>
    62   *
    63   * <p>
    64   * Also implements Groovy's {@link groovy.util.ResourceConnector} in dynamic
    65   * manner. It allows to modifiy the resource name that is searched for with a
    66   * <i>replace all</i> operation. See {@link java.util.regex.Pattern} and
    67   * {@link java.util.regex.Matcher} for details.
    68   * The servlet init parameter names are:
    69   * <pre>
    70   * resource.name.regex = empty - defaults to null
    71   * resource.name.replacement = empty - defaults to null
    72   * resource.name.replace.all = true (default) | false means replaceFirst()
    73   * </pre>
    74   * Note: If you specify a regex, you have to specify a replacement string too!
    75   * Otherwise an exception gets raised.
    76   *
    77   * <h4>Logging and bug-hunting options</h4>
    78   *
    79   * <p>
    80   * This implementation provides a verbosity flag switching log statements.
    81   * The servlet init parameter name is:
    82   * <pre>
    83   * verbose = false(default) | true
    84   * </pre>
    85   *
    86   * <p>
    87   * In order to support class-loading-troubles-debugging with Tomcat 4 or
    88   * higher, you can log the class loader responsible for loading some classes.
    89   * See {@linkplain http://jira.codehaus.org/browse/GROOVY-861} for details.
    90   * The servlet init parameter name is:
    91   * <pre>
    92   * log.GROOVY861 = false(default) | true
    93   * </pre>
    94   *
    95   * <p>
    96   * If you experience class-loading-troubles with Tomcat 4 (or higher) or any
    97   * other servlet container using custom class loader setups, you can fallback
    98   * to use (slower) reflection in Groovy's MetaClass implementation. Please
    99   * contact the dev team with your problem! Thanks.
    100  * The servlet init parameter name is:
    101  * <pre>
    102  * reflection = false(default) | true
    103  * </pre>
    104  *
    105  *
    106  * @author Christian Stein
    107  */
    108 public abstract class AbstractHttpServlet extends HttpServlet implements ResourceConnector {
    109
    110     /***
    111      * Content type of the HTTP response.
    112      */
    113     public static final String CONTENT_TYPE_TEXT_HTML = "text/html";
    114
    115     /***
    116      * Servlet API include key name: path_info
    117      */
    118     public static final String INC_PATH_INFO = "javax.servlet.include.path_info";
    119
    120     /* *** Not used, yet. See comments in getScriptUri(HttpServletRequest). ***
    121      * Servlet API include key name: request_uri
    122      */
    123     public static final String INC_REQUEST_URI = "javax.servlet.include.request_uri";
    124
    125     /***
    126      * Servlet API include key name: servlet_path
    127      */
    128     public static final String INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
    129
    130     /***
    131      * Servlet (or the web application) context.
    132      */
    133     protected ServletContext servletContext;
    134
    135     /***
    136      * <b>Null</b> or compiled pattern matcher read from "resource.name.regex"
    137      *  and used in {@link AbstractHttpServlet#getResourceConnection(String)}.
    138      */
    139     protected Matcher resourceNameMatcher;
    140
    141     /***
    142      * The replacement used by the resource name matcher.
    143      */
    144     protected String resourceNameReplacement;
    145
    146     /***
    147      * The replace method to use on the matcher.
    148      * <pre>
    149      * true - replaceAll(resourceNameReplacement); (default)
    150      * false - replaceFirst(resourceNameReplacement);
    151      * </pre>
    152      */
    153     protected boolean resourceNameReplaceAll;
    154
    155     /***
    156      * Controls almost all log output.
    157      */
    158     protected boolean verbose;
    159
    160     /***
    161      * Mirrors the static value of the reflection flag in MetaClass.
    162      * See {@link AbstractHttpServlet#logGROOVY861}
    163      */
    164     protected boolean reflection;
    165
    166     /***
    167      * Debug flag logging the class the class loader of the request.
    168      */
    169     private boolean logGROOVY861;
    170
    171     /***
    172      * Initializes all fields with default values.
    173      */
    174     public AbstractHttpServlet() {
    175         this.servletContext = null;
    176         this.resourceNameMatcher = null;
    177         this.resourceNameReplacement = null;
    178         this.resourceNameReplaceAll = true;
    179         this.verbose = false;
    180         this.reflection = false;
    181         this.logGROOVY861 = false;
    182     }
    183
    184     /***
    185      * Interface method for ResourceContainer. This is used by the GroovyScriptEngine.
    186      */
    187     public URLConnection getResourceConnection(String name) throws ResourceException {
    188         /*
    189          * First, mangle resource name with the compiled pattern.
    190          */
    191         Matcher matcher = resourceNameMatcher;
    192         if (matcher != null) {
    193             matcher.reset(name);
    194             String replaced;
    195             if (resourceNameReplaceAll) {
    196                 replaced = resourceNameMatcher.replaceAll(resourceNameReplacement);
    197             } else {
    198                 replaced = resourceNameMatcher.replaceFirst(resourceNameReplacement);
    199             }
    200             if (!name.equals(replaced)) {
    201                 if (verbose) {
    202                     log("Replaced resource name \"" + name + "\" with \"" + replaced + "\".");
    203                 }
    204                 name = replaced;
    205             }
    206         }
    207
    208         /*
    209          * Try to locate the resource and return an opened connection to it.
    210          */
    211         try {
    212             URL url = servletContext.getResource("/" + name);
    213             if (url == null) {
    214                 url = servletContext.getResource("/WEB-INF/groovy/" + name);
    215             }
    216             if (url == null) {
    217                 throw new ResourceException("Resource \"" + name + "\" not found!");
    218             }
    219             return url.openConnection();
    220         } catch (IOException e) {
    221             throw new ResourceException("Problems getting resource named \"" + name + "\"!", e);
    222         }
    223     }
    224
    225     /***
    226      * Returns the include-aware uri of the script or template file.
    227      *
    228      * @param request
    229      *  the http request to analyze
    230      * @return the include-aware uri either parsed from request attributes or
    231      *  hints provided by the servlet container
    232      */
    233     protected String getScriptUri(HttpServletRequest request) {
    234         /*
    235          * Log some debug information for http://jira.codehaus.org/browse/GROOVY-861
    236          */
    237         if (logGROOVY861) {
    238             log("Logging request class and its class loader:");
    239             log(" c = request.getClass() :\"" + request.getClass() + "\"");
    240             log(" l = c.getClassLoader() :\"" + request.getClass().getClassLoader() + "\"");
    241             log(" l.getClass()           :\"" + request.getClass().getClassLoader().getClass() + "\"");
    242             /*
    243              * Keep logging, if we're verbose. Else turn it off.
    244              */
    245             logGROOVY861 = verbose;
    246         }
    247
    248         //
    249         // NOTE: This piece of code is heavily inspired by Apaches Jasper2!
    250         //
    251         // http://cvs.apache.org/viewcvs.cgi/jakarta-tomcat-jasper/jasper2/ \
    252         //        src/share/org/apache/jasper/servlet/JspServlet.java?view=markup
    253         //
    254         // Why doesn't it use request.getRequestURI() or INC_REQUEST_URI?
    255         //
    256
    257         String uri = null;
    258         String info = null;
    259
    260         //
    261         // Check to see if the requested script/template source file has been the
    262         // target of a RequestDispatcher.include().
    263         //
    264         uri = (String) request.getAttribute(INC_SERVLET_PATH);
    265         if (uri != null) {
    266             //
    267             // Requested script/template file has been target of
    268             // RequestDispatcher.include(). Its path is assembled from the relevant
    269             // javax.servlet.include.* request attributes and returned!
    270             //
    271             info = (String) request.getAttribute(INC_PATH_INFO);
    272             if (info != null) {
    273                 uri += info;
    274             }
    275             return uri;
    276         }
    277
    278         //
    279         // Requested script/template file has not been the target of a
    280         // RequestDispatcher.include(). Reconstruct its path from the request's
    281         // getServletPath() and getPathInfo() results.
    282         //
    283         uri = request.getServletPath();
    284         info = request.getPathInfo();
    285         if (info != null) {
    286             uri += info;
    287         }
    288
    289         /*
    290          * TODO : Enable auto ".groovy" extension replacing here!
    291          * http://cvs.groovy.codehaus.org/viewrep/groovy/groovy/groovy-core/src/main/groovy/servlet/GroovyServlet.java?r=1.10#l259
    292          */
    293
    294         return uri;
    295     }
    296
    297     /***
    298      * Parses the http request for the real script or template source file.
    299      *
    300      * @param request
    301      *  the http request to analyze
    302      * @param context
    303      *  the context of this servlet used to get the real path string
    304      * @return a file object using an absolute file path name
    305      */
    306     protected File getScriptUriAsFile(HttpServletRequest request) {
    307         String uri = getScriptUri(request);
    308         String real = servletContext.getRealPath(uri);
    309         File file = new File(real).getAbsoluteFile();
    310         return file;
    311     }
    312
    313     /***
    314      * Overrides the generic init method to set some debug flags.
    315      *
    316      * @param config
    317      *  the servlet coniguration provided by the container
    318      * @throws ServletException if init() method defined in super class
    319      *  javax.servlet.GenericServlet throws it
    320      */
    321     public void init(ServletConfig config) throws ServletException {
    322         /*
    323          * Never forget super.init()!
    324          */
    325         super.init(config);
    326
    327         /*
    328          * Grab the servlet context.
    329          */
    330         this.servletContext = config.getServletContext();
    331
    332         /*
    333          * Get verbosity hint.
    334          */
    335         String value = config.getInitParameter("verbose");
    336         if (value != null) {
    337             this.verbose = Boolean.valueOf(value).booleanValue();
    338         }
    339
    340         /*
    341          * And now the real init work...
    342          */
    343         if (verbose) {
    344             log("Parsing init parameters...");
    345         }
    346
    347         String regex = config.getInitParameter("resource.name.regex");
    348         if (regex != null) {
    349             String replacement = config.getInitParameter("resource.name.replacement");
    350             if (replacement == null) {
    351                 Exception npex = new NullPointerException("resource.name.replacement");
    352                 String message = "Init-param 'resource.name.replacement' not specified!";
    353                 log(message, npex);
    354                 throw new ServletException(message, npex);
    355             }
    356             int flags = 0; // TODO : Parse pattern compile flags.
    357             this.resourceNameMatcher = Pattern.compile(regex, flags).matcher("");
    358             this.resourceNameReplacement = replacement;
    359             String all = config.getInitParameter("resource.name.replace.all");
    360             if (all != null) {
    361                 this.resourceNameReplaceAll = Boolean.valueOf(all).booleanValue();
    362             }
    363         }
    364
    365         value = config.getInitParameter("reflection");
    366         if (value != null) {
    367             this.reflection = Boolean.valueOf(value).booleanValue();
    368             MetaClass.setUseReflection(reflection);
    369         }
    370
    371         value = config.getInitParameter("logGROOVY861");
    372         if (value != null) {
    373             this.logGROOVY861 = Boolean.valueOf(value).booleanValue();
    374             // nothing else to do here
    375         }
    376
    377         /*
    378          * If verbose, log the parameter values.
    379          */
    380         if (verbose) {
    381             log("(Abstract) init done. Listing some parameter name/value pairs:");
    382             log("verbose = " + verbose); // this *is* verbose! ;)
    383             log("reflection = " + reflection);
    384             log("logGROOVY861 = " + logGROOVY861);
    385             if (resourceNameMatcher != null) {
    386                 log("resource.name.regex = " + resourceNameMatcher.pattern().pattern());
    387             }
    388             else {
    389                 log("resource.name.regex = null");
    390             }
    391             log("resource.name.replacement = " + resourceNameReplacement);
    392         }
    393     }
    394 }