001 /* 002 * $Id: AbstractHttpServlet.java,v 1.8 2005/07/29 11:36:17 cstein Exp $ 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 {@linkplain http://jira.codehaus.org/browse/GROOVY-861} 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 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 log("resource.name.regex = " + resourceNameMatcher.pattern().pattern()); 386 log("resource.name.replacement = " + resourceNameReplacement); 387 } 388 } 389 }