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 }