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 }