View Javadoc

    1   /*
    2    * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
    3    *
    4    * Redistribution and use of this software and associated documentation
    5    * ("Software"), with or without modification, are permitted provided that the
    6    * following conditions are met: 1. Redistributions of source code must retain
    7    * copyright statements and notices. Redistributions must also contain a copy
    8    * of this document. 2. Redistributions in binary form must reproduce the above
    9    * copyright notice, this list of conditions and the following disclaimer in
    10   * the documentation and/or other materials provided with the distribution. 3.
    11   * The name "groovy" must not be used to endorse or promote products derived
    12   * from this Software without prior written permission of The Codehaus. For
    13   * written permission, please contact info@codehaus.org. 4. Products derived
    14   * from this Software may not be called "groovy" nor may "groovy" appear in
    15   * their names without prior written permission of The Codehaus. "groovy" is a
    16   * registered trademark of The Codehaus. 5. Due credit should be given to The
    17   * Codehaus - http://groovy.codehaus.org/
    18   *
    19   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
    20   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    22   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
    23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    24   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    26   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    27   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    28   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
    29   * DAMAGE.
    30   *
    31   */
    32  package groovy.servlet;
    33
    34  import groovy.lang.Binding;
    35  import groovy.lang.MetaClass;
    36  import groovy.lang.Closure;
    37  import groovy.util.GroovyScriptEngine;
    38  import groovy.util.ResourceConnector;
    39  import groovy.util.ResourceException;
    40  import groovy.util.ScriptException;
    41
    42  import java.io.IOException;
    43  import java.net.URL;
    44  import java.net.URLConnection;
    45  import java.util.Collections;
    46  import java.util.Enumeration;
    47  import java.util.HashMap;
    48  import java.util.Map;
    49
    50  import javax.servlet.ServletConfig;
    51  import javax.servlet.ServletContext;
    52  import javax.servlet.ServletException;
    53  import javax.servlet.ServletRequest;
    54  import javax.servlet.ServletResponse;
    55  import javax.servlet.http.HttpServlet;
    56  import javax.servlet.http.HttpServletRequest;
    57  import javax.servlet.http.HttpServletResponse;
    58
    59  import org.codehaus.groovy.runtime.GroovyCategorySupport;
    60
    61  /***
    62   * This servlet will run Groovy scripts as Groovlets.  Groovlets are scripts
    63   * with these objects implicit in their scope:
    64   *
    65   * <ul>
    66   * 	<li>request - the HttpServletRequest</li>
    67   *  <li>response - the HttpServletResponse</li>
    68   *  <li>application - the ServletContext associated with the servlet</li>
    69   *  <li>session - the HttpSession associated with the HttpServletRequest</li>
    70   *  <li>out - the PrintWriter associated with the ServletRequest</li>
    71   * </ul>
    72   *
    73   * <p>Your script sources can be placed either in your web application's normal
    74   * web root (allows for subdirectories) or in /WEB-INF/groovy/* (also allows
    75   * subdirectories).
    76   *
    77   * <p>To make your web application more groovy, you must add the GroovyServlet
    78   * to your application's web.xml configuration using any mapping you like, so
    79   * long as it follows the pattern *.* (more on this below).  Here is the
    80   * web.xml entry:
    81   *
    82   * <pre>
    83   *    <servlet>
    84   *      <servlet-name>Groovy</servlet-name>
    85   *      <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
    86   *    </servlet>
    87   *
    88   *    <servlet-mapping>
    89   *      <servlet-name>Groovy</servlet-name>
    90   *      <url-pattern>*.groovy</url-pattern>
    91   *    </servlet-mapping>
    92   * </pre>
    93   *
    94   * <p>The URL pattern does not require the "*.groovy" mapping.  You can, for
    95   * example, make it more Struts-like but groovy by making your mapping "*.gdo".
    96   *
    97   * <p>Whatever your mapping, the GroovyServlet will check to see if your
    98   * URL ends with ".groovy" and, if it does not, it will strip your mapping
    99   * and append ".groovy".
    100  *
    101  * <p>NOTE!  The GroovyServlet only handles mappings of the *.* type, not the
    102  * path-like type of /groovy/*</p>
    103  *
    104  * @author Sam Pullara
    105  * @author Mark Turansky (markturansky at hotmail.com)
    106  * @author Guillaume Laforge
    107  */
    108 public class GroovyServlet extends HttpServlet implements ResourceConnector {
    109
    110     // ------------------------------------------------------ instance variables
    111
    112     /***
    113      * A constant for ".groovy" which gets appended to script paths as needed.
    114      */
    115     public static final String GROOVY_EXTENSION = ".groovy";
    116
    117     /***
    118      * The context in which this servlet is executing
    119      */
    120     private ServletContext sc;
    121
    122     /***
    123      * The classloader associated with this servlet
    124      */
    125     private static ClassLoader parent;
    126
    127     /***
    128      * The script engine executing the Groovy scripts for this servlet
    129      */
    130     private static GroovyScriptEngine gse;
    131
    132     // ---------------------------------------------------------- public methods
    133
    134     /***
    135      * Returns the ServletContext for this servlet
    136      */
    137     public ServletContext getServletContext() {
    138         return sc;
    139     }
    140
    141     /***
    142      * Initialize the GroovyServlet.
    143      */
    144     public void init(ServletConfig config) {
    145
    146         // Use reflection, some containers don't load classes properly
    147         MetaClass.setUseReflection(true);
    148
    149         // Get the servlet context
    150         sc = config.getServletContext();
    151         sc.log("Groovy servlet initialized");
    152
    153         // Ensure that we use the correct classloader so that we can find
    154         // classes in an application server.
    155         parent = Thread.currentThread().getContextClassLoader();
    156         if (parent == null)
    157             parent = GroovyServlet.class.getClassLoader();
    158
    159         // Set up the scripting engine
    160         gse = new GroovyScriptEngine(this);
    161     }
    162
    163     /***
    164      * Interface method for ResourceContainer. This is used by the GroovyScriptEngine.
    165      */
    166     public URLConnection getResourceConnection(String name) throws ResourceException {
    167         try {
    168             URL url = sc.getResource("/" + name);
    169             if (url == null) {
    170                 url = sc.getResource("/WEB-INF/groovy/" + name);
    171                 if (url == null) {
    172                     throw new ResourceException("Resource " + name + " not found");
    173                 }
    174             }
    175             return url.openConnection();
    176         } catch (IOException ioe) {
    177             throw new ResourceException("Problem reading resource " + name);
    178         }
    179     }
    180
    181     /***
    182      * Handle web requests to the GroovyServlet
    183      */
    184     public void service(ServletRequest request, ServletResponse response)
    185         throws ServletException, IOException {
    186
    187         // Convert the generic servlet request and response to their Http versions
    188         final HttpServletRequest httpRequest = (HttpServletRequest) request;
    189         final HttpServletResponse httpResponse = (HttpServletResponse) response;
    190
    191         // get the script path from the request
    192         final String scriptFilename = getGroovyScriptPath(httpRequest);
    193
    194         // Set it to HTML by default
    195         response.setContentType("text/html");
    196
    197         // Set up the script context
    198         final Binding binding = new ServletBinding((HttpServletRequest) request, response, sc);
    199
    200         // Run the script
    201         try {
    202             Closure closure = new Closure(gse) {
    203                 public Object call() {
    204                     try {
    205                         return ((GroovyScriptEngine)getDelegate()).run(scriptFilename, binding);
    206                     } catch (ResourceException e) {
    207                         throw new RuntimeException(e);
    208                     } catch (ScriptException e) {
    209                         throw new RuntimeException(e);
    210                     }
    211                 }
    212             };
    213             GroovyCategorySupport.use(ServletCategory.class, closure);
    214         } catch (RuntimeException re) {
    215
    216             StringBuffer error = new StringBuffer("GroovyServlet Error: ");
    217             error.append(" script: '");
    218             error.append(scriptFilename);
    219             error.append("': ");
    220
    221             Throwable e = re.getCause();
    222             if (e instanceof ResourceException) {
    223                 error.append(" Script not found, sending 404.");
    224                 sc.log(error.toString());
    225                 System.out.println(error.toString());
    226                 httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
    227             } else {
    228
    229                 // write the script errors (if any) to the servlet context's log
    230                 if (re.getMessage() != null)
    231                     error.append(re.getMessage());
    232
    233                 if (e != null) {
    234                     sc.log("An error occurred processing the request", e);
    235                 } else {
    236                     sc.log("An error occurred processing the request", re);
    237                 }
    238                 sc.log(error.toString());
    239                 System.out.println(error.toString());
    240
    241                 httpResponse.sendError(
    242                     HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    243             }
    244         }
    245     }
    246
    247     // --------------------------------------------------------- private methods
    248
    249     /***
    250      * From the HttpServletRequest, parse the groovy script path using the URL,
    251      * adding the extension ".groovy" as needed.
    252      */
    253     private String getGroovyScriptPath(HttpServletRequest request) {
    254
    255         // Get the name of the Groovy script
    256         int contextLength = request.getContextPath().length();
    257         String scriptFilename = request.getRequestURI()
    258             .substring(contextLength).substring(1);
    259
    260         // if the servlet mapping is .groovy, we don't need to strip the mapping from the filename.
    261         // if the mapping is anything else, we need to strip it and append .groovy
    262         if (scriptFilename.endsWith(GROOVY_EXTENSION))
    263             return scriptFilename;
    264
    265         // strip the servlet mapping (from the last ".") and append .groovy
    266         int lastDot = scriptFilename.lastIndexOf(".");
    267         scriptFilename = scriptFilename.substring(0, lastDot)
    268             + GROOVY_EXTENSION;
    269         return scriptFilename;
    270
    271     }
    272 }