1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
121
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
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
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
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
244
245 logGROOVY861 = verbose;
246 }
247
248
249
250
251
252
253
254
255
256
257 String uri = null;
258 String info = null;
259
260
261
262
263
264 uri = (String) request.getAttribute(INC_SERVLET_PATH);
265 if (uri != null) {
266
267
268
269
270
271 info = (String) request.getAttribute(INC_PATH_INFO);
272 if (info != null) {
273 uri += info;
274 }
275 return uri;
276 }
277
278
279
280
281
282
283 uri = request.getServletPath();
284 info = request.getPathInfo();
285 if (info != null) {
286 uri += info;
287 }
288
289
290
291
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
324
325 super.init(config);
326
327
328
329
330 this.servletContext = config.getServletContext();
331
332
333
334
335 String value = config.getInitParameter("verbose");
336 if (value != null) {
337 this.verbose = Boolean.valueOf(value).booleanValue();
338 }
339
340
341
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;
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
375 }
376
377
378
379
380 if (verbose) {
381 log("(Abstract) init done. Listing some parameter name/value pairs:");
382 log("verbose = " + 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 }