001: /*
002: * $Id: AbstractHttpServlet.java 4032 2006-08-30 07:18:49Z mguillem $
003: *
004: * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005: *
006: * Redistribution and use of this software and associated documentation
007: * ("Software"), with or without modification, are permitted provided that the
008: * following conditions are met:
009: *
010: * 1. Redistributions of source code must retain copyright statements and
011: * notices. Redistributions must also contain a copy of this document.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright notice,
014: * this list of conditions and the following disclaimer in the documentation
015: * and/or other materials provided with the distribution.
016: *
017: * 3. The name "groovy" must not be used to endorse or promote products derived
018: * from this Software without prior written permission of The Codehaus. For
019: * written permission, please contact info@codehaus.org.
020: *
021: * 4. Products derived from this Software may not be called "groovy" nor may
022: * "groovy" appear in their names without prior written permission of The
023: * Codehaus. "groovy" is a registered trademark of The Codehaus.
024: *
025: * 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
028: * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
029: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
030: * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
031: * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
032: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
033: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
034: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
035: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
036: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: *
038: */
039: package groovy.servlet;
040:
041: import groovy.lang.MetaClass;
042: import groovy.util.ResourceConnector;
043: import groovy.util.ResourceException;
044:
045: import java.io.File;
046: import java.io.IOException;
047: import java.net.URL;
048: import java.net.URLConnection;
049: import java.util.regex.Matcher;
050: import java.util.regex.Pattern;
051:
052: import javax.servlet.ServletConfig;
053: import javax.servlet.ServletContext;
054: import javax.servlet.ServletException;
055: import javax.servlet.http.HttpServlet;
056: import javax.servlet.http.HttpServletRequest;
057:
058: /**
059: * A common ground dealing with the HTTP servlet API wrinkles.
060: *
061: * <h4>Resource name mangling (pattern replacement)</h4>
062: *
063: * <p>
064: * Also implements Groovy's {@link groovy.util.ResourceConnector} in dynamic
065: * manner. It allows to modifiy the resource name that is searched for with a
066: * <i>replace all</i> operation. See {@link java.util.regex.Pattern} and
067: * {@link java.util.regex.Matcher} for details.
068: * The servlet init parameter names are:
069: * <pre>
070: * resource.name.regex = empty - defaults to null
071: * resource.name.replacement = empty - defaults to null
072: * resource.name.replace.all = true (default) | false means replaceFirst()
073: * </pre>
074: * Note: If you specify a regex, you have to specify a replacement string too!
075: * Otherwise an exception gets raised.
076: *
077: * <h4>Logging and bug-hunting options</h4>
078: *
079: * <p>
080: * This implementation provides a verbosity flag switching log statements.
081: * The servlet init parameter name is:
082: * <pre>
083: * verbose = false(default) | true
084: * </pre>
085: *
086: * <p>
087: * In order to support class-loading-troubles-debugging with Tomcat 4 or
088: * higher, you can log the class loader responsible for loading some classes.
089: * See <a href="http://jira.codehaus.org/browse/GROOVY-861">GROOVY-861</a> for details.
090: * The servlet init parameter name is:
091: * <pre>
092: * log.GROOVY861 = false(default) | true
093: * </pre>
094: *
095: * <p>
096: * If you experience class-loading-troubles with Tomcat 4 (or higher) or any
097: * other servlet container using custom class loader setups, you can fallback
098: * to use (slower) reflection in Groovy's MetaClass implementation. Please
099: * 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
109: ResourceConnector {
110:
111: /**
112: * Content type of the HTTP response.
113: */
114: public static final String CONTENT_TYPE_TEXT_HTML = "text/html";
115:
116: /**
117: * Servlet API include key name: path_info
118: */
119: public static final String INC_PATH_INFO = "javax.servlet.include.path_info";
120:
121: /* *** Not used, yet. See comments in getScriptUri(HttpServletRequest). ***
122: * Servlet API include key name: request_uri
123: */
124: public static final String INC_REQUEST_URI = "javax.servlet.include.request_uri";
125:
126: /**
127: * Servlet API include key name: servlet_path
128: */
129: public static final String INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
130:
131: /**
132: * Servlet (or the web application) context.
133: */
134: protected ServletContext servletContext;
135:
136: /**
137: * <b>Null</b> or compiled pattern matcher read from "resource.name.regex"
138: * and used in {@link AbstractHttpServlet#getResourceConnection(String)}.
139: */
140: protected Matcher resourceNameMatcher;
141:
142: /**
143: * The replacement used by the resource name matcher.
144: */
145: protected String resourceNameReplacement;
146:
147: /**
148: * The replace method to use on the matcher.
149: * <pre>
150: * true - replaceAll(resourceNameReplacement); (default)
151: * false - replaceFirst(resourceNameReplacement);
152: * </pre>
153: */
154: protected boolean resourceNameReplaceAll;
155:
156: /**
157: * Controls almost all log output.
158: */
159: protected boolean verbose;
160:
161: /**
162: * Mirrors the static value of the reflection flag in MetaClass.
163: * See {@link AbstractHttpServlet#logGROOVY861}
164: */
165: protected boolean reflection;
166:
167: /**
168: * Debug flag logging the class the class loader of the request.
169: */
170: private boolean logGROOVY861;
171:
172: /**
173: * Initializes all fields with default values.
174: */
175: public AbstractHttpServlet() {
176: this .servletContext = null;
177: this .resourceNameMatcher = null;
178: this .resourceNameReplacement = null;
179: this .resourceNameReplaceAll = true;
180: this .verbose = false;
181: this .reflection = false;
182: this .logGROOVY861 = false;
183: }
184:
185: /**
186: * Interface method for ResourceContainer. This is used by the GroovyScriptEngine.
187: */
188: public URLConnection getResourceConnection(String name)
189: throws ResourceException {
190: /*
191: * First, mangle resource name with the compiled pattern.
192: */
193: Matcher matcher = resourceNameMatcher;
194: if (matcher != null) {
195: matcher.reset(name);
196: String replaced;
197: if (resourceNameReplaceAll) {
198: replaced = resourceNameMatcher
199: .replaceAll(resourceNameReplacement);
200: } else {
201: replaced = resourceNameMatcher
202: .replaceFirst(resourceNameReplacement);
203: }
204: if (!name.equals(replaced)) {
205: if (verbose) {
206: log("Replaced resource name \"" + name
207: + "\" with \"" + replaced + "\".");
208: }
209: name = replaced;
210: }
211: }
212:
213: /*
214: * Try to locate the resource and return an opened connection to it.
215: */
216: try {
217: URL url = servletContext.getResource("/" + name);
218: if (url == null) {
219: url = servletContext.getResource("/WEB-INF/groovy/"
220: + name);
221: }
222: if (url == null) {
223: throw new ResourceException("Resource \"" + name
224: + "\" not found!");
225: }
226: return url.openConnection();
227: } catch (IOException e) {
228: throw new ResourceException(
229: "Problems getting resource named \"" + name + "\"!",
230: e);
231: }
232: }
233:
234: /**
235: * Returns the include-aware uri of the script or template file.
236: *
237: * @param request
238: * the http request to analyze
239: * @return the include-aware uri either parsed from request attributes or
240: * hints provided by the servlet container
241: */
242: protected String getScriptUri(HttpServletRequest request) {
243: /*
244: * Log some debug information for http://jira.codehaus.org/browse/GROOVY-861
245: */
246: if (logGROOVY861) {
247: log("Logging request class and its class loader:");
248: log(" c = request.getClass() :\"" + request.getClass()
249: + "\"");
250: log(" l = c.getClassLoader() :\""
251: + request.getClass().getClassLoader() + "\"");
252: log(" l.getClass() :\""
253: + request.getClass().getClassLoader().getClass()
254: + "\"");
255: /*
256: * Keep logging, if we're verbose. Else turn it off.
257: */
258: logGROOVY861 = verbose;
259: }
260:
261: //
262: // NOTE: This piece of code is heavily inspired by Apaches Jasper2!
263: //
264: // http://cvs.apache.org/viewcvs.cgi/jakarta-tomcat-jasper/jasper2/ \
265: // src/share/org/apache/jasper/servlet/JspServlet.java?view=markup
266: //
267: // Why doesn't it use request.getRequestURI() or INC_REQUEST_URI?
268: //
269:
270: String uri = null;
271: String info = null;
272:
273: //
274: // Check to see if the requested script/template source file has been the
275: // target of a RequestDispatcher.include().
276: //
277: uri = (String) request.getAttribute(INC_SERVLET_PATH);
278: if (uri != null) {
279: //
280: // Requested script/template file has been target of
281: // RequestDispatcher.include(). Its path is assembled from the relevant
282: // javax.servlet.include.* request attributes and returned!
283: //
284: info = (String) request.getAttribute(INC_PATH_INFO);
285: if (info != null) {
286: uri += info;
287: }
288: return uri;
289: }
290:
291: //
292: // Requested script/template file has not been the target of a
293: // RequestDispatcher.include(). Reconstruct its path from the request's
294: // getServletPath() and getPathInfo() results.
295: //
296: uri = request.getServletPath();
297: info = request.getPathInfo();
298: if (info != null) {
299: uri += info;
300: }
301:
302: /*
303: * TODO : Enable auto ".groovy" extension replacing here!
304: * http://cvs.groovy.codehaus.org/viewrep/groovy/groovy/groovy-core/src/main/groovy/servlet/GroovyServlet.java?r=1.10#l259
305: */
306:
307: return uri;
308: }
309:
310: /**
311: * Parses the http request for the real script or template source file.
312: * @param request the http request to analyze
313: * @return a file object using an absolute file path name
314: */
315: protected File getScriptUriAsFile(HttpServletRequest request) {
316: String uri = getScriptUri(request);
317: String real = servletContext.getRealPath(uri);
318: File file = new File(real).getAbsoluteFile();
319: return file;
320: }
321:
322: /**
323: * Overrides the generic init method to set some debug flags.
324: *
325: * @param config
326: * the servlet coniguration provided by the container
327: * @throws ServletException if init() method defined in super class
328: * javax.servlet.GenericServlet throws it
329: */
330: public void init(ServletConfig config) throws ServletException {
331: /*
332: * Never forget super.init()!
333: */
334: super .init(config);
335:
336: /*
337: * Grab the servlet context.
338: */
339: this .servletContext = config.getServletContext();
340:
341: /*
342: * Get verbosity hint.
343: */
344: String value = config.getInitParameter("verbose");
345: if (value != null) {
346: this .verbose = Boolean.valueOf(value).booleanValue();
347: }
348:
349: /*
350: * And now the real init work...
351: */
352: if (verbose) {
353: log("Parsing init parameters...");
354: }
355:
356: String regex = config.getInitParameter("resource.name.regex");
357: if (regex != null) {
358: String replacement = config
359: .getInitParameter("resource.name.replacement");
360: if (replacement == null) {
361: Exception npex = new NullPointerException(
362: "resource.name.replacement");
363: String message = "Init-param 'resource.name.replacement' not specified!";
364: log(message, npex);
365: throw new ServletException(message, npex);
366: }
367: int flags = 0; // TODO : Parse pattern compile flags.
368: this .resourceNameMatcher = Pattern.compile(regex, flags)
369: .matcher("");
370: this .resourceNameReplacement = replacement;
371: String all = config
372: .getInitParameter("resource.name.replace.all");
373: if (all != null) {
374: this .resourceNameReplaceAll = Boolean.valueOf(all)
375: .booleanValue();
376: }
377: }
378:
379: value = config.getInitParameter("reflection");
380: if (value != null) {
381: this .reflection = Boolean.valueOf(value).booleanValue();
382: MetaClass.setUseReflection(reflection);
383: }
384:
385: value = config.getInitParameter("logGROOVY861");
386: if (value != null) {
387: this .logGROOVY861 = Boolean.valueOf(value).booleanValue();
388: // nothing else to do here
389: }
390:
391: /*
392: * If verbose, log the parameter values.
393: */
394: if (verbose) {
395: log("(Abstract) init done. Listing some parameter name/value pairs:");
396: log("verbose = " + verbose); // this *is* verbose! ;)
397: log("reflection = " + reflection);
398: log("logGROOVY861 = " + logGROOVY861);
399: if (resourceNameMatcher != null) {
400: log("resource.name.regex = "
401: + resourceNameMatcher.pattern().pattern());
402: } else {
403: log("resource.name.regex = null");
404: }
405: log("resource.name.replacement = "
406: + resourceNameReplacement);
407: }
408: }
409: }
|