Source code: org/apache/jasper/servlet/JspServletWrapper.java
1 /*
2 * Copyright 1999,2004-2005 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.jasper.servlet;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.IOException;
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import javax.servlet.Servlet;
31 import javax.servlet.ServletConfig;
32 import javax.servlet.ServletContext;
33 import javax.servlet.ServletException;
34 import javax.servlet.SingleThreadModel;
35 import javax.servlet.UnavailableException;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38 import javax.servlet.jsp.tagext.TagInfo;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.apache.jasper.JasperException;
43 import org.apache.jasper.JspCompilationContext;
44 import org.apache.jasper.Options;
45 import org.apache.jasper.compiler.ErrorDispatcher;
46 import org.apache.jasper.compiler.JavacErrorDetail;
47 import org.apache.jasper.compiler.JspRuntimeContext;
48 import org.apache.jasper.compiler.Localizer;
49 import org.apache.jasper.runtime.JspSourceDependent;
50
51 /**
52 * The JSP engine (a.k.a Jasper).
53 *
54 * The servlet container is responsible for providing a
55 * URLClassLoader for the web application context Jasper
56 * is being used in. Jasper will try get the Tomcat
57 * ServletContext attribute for its ServletContext class
58 * loader, if that fails, it uses the parent class loader.
59 * In either case, it must be a URLClassLoader.
60 *
61 * @author Anil K. Vijendran
62 * @author Harish Prabandham
63 * @author Remy Maucherat
64 * @author Kin-man Chung
65 * @author Glenn Nielsen
66 * @author Tim Fennell
67 */
68
69 public class JspServletWrapper {
70
71 // Logger
72 private Log log = LogFactory.getLog(JspServletWrapper.class);
73
74 private Servlet theServlet;
75 private String jspUri;
76 private Class servletClass;
77 private Class tagHandlerClass;
78 private JspCompilationContext ctxt;
79 private long available = 0L;
80 private ServletConfig config;
81 private Options options;
82 private boolean firstTime = true;
83 private boolean reload = true;
84 private boolean isTagFile;
85 private int tripCount;
86 private JasperException compileException;
87 private long servletClassLastModifiedTime;
88 private long lastModificationTest = 0L;
89
90 /*
91 * JspServletWrapper for JSP pages.
92 */
93 JspServletWrapper(ServletConfig config, Options options, String jspUri,
94 boolean isErrorPage, JspRuntimeContext rctxt)
95 throws JasperException {
96
97 this.isTagFile = false;
98 this.config = config;
99 this.options = options;
100 this.jspUri = jspUri;
101 ctxt = new JspCompilationContext(jspUri, isErrorPage, options,
102 config.getServletContext(),
103 this, rctxt);
104 }
105
106 /*
107 * JspServletWrapper for tag files.
108 */
109 public JspServletWrapper(ServletContext servletContext,
110 Options options,
111 String tagFilePath,
112 TagInfo tagInfo,
113 JspRuntimeContext rctxt,
114 URL tagFileJarUrl)
115 throws JasperException {
116
117 this.isTagFile = true;
118 this.config = null; // not used
119 this.options = options;
120 this.jspUri = tagFilePath;
121 this.tripCount = 0;
122 ctxt = new JspCompilationContext(jspUri, tagInfo, options,
123 servletContext, this, rctxt,
124 tagFileJarUrl);
125 }
126
127 public JspCompilationContext getJspEngineContext() {
128 return ctxt;
129 }
130
131 public void setReload(boolean reload) {
132 this.reload = reload;
133 }
134
135 public Servlet getServlet()
136 throws ServletException, IOException, FileNotFoundException
137 {
138 if (reload) {
139 synchronized (this) {
140 // Synchronizing on jsw enables simultaneous loading
141 // of different pages, but not the same page.
142 if (reload) {
143 // This is to maintain the original protocol.
144 destroy();
145
146 try {
147 servletClass = ctxt.load();
148 theServlet = (Servlet) servletClass.newInstance();
149 } catch( IllegalAccessException ex1 ) {
150 throw new JasperException( ex1 );
151 } catch( InstantiationException ex ) {
152 throw new JasperException( ex );
153 }
154
155 theServlet.init(config);
156
157 if (!firstTime) {
158 ctxt.getRuntimeContext().incrementJspReloadCount();
159 }
160
161 reload = false;
162 }
163 }
164 }
165 return theServlet;
166 }
167
168 public ServletContext getServletContext() {
169 return config.getServletContext();
170 }
171
172 /**
173 * Sets the compilation exception for this JspServletWrapper.
174 *
175 * @param je The compilation exception
176 */
177 public void setCompilationException(JasperException je) {
178 this.compileException = je;
179 }
180
181 /**
182 * Sets the last-modified time of the servlet class file associated with
183 * this JspServletWrapper.
184 *
185 * @param lastModified Last-modified time of servlet class
186 */
187 public void setServletClassLastModifiedTime(long lastModified) {
188 if (this.servletClassLastModifiedTime < lastModified) {
189 synchronized (this) {
190 if (this.servletClassLastModifiedTime < lastModified) {
191 this.servletClassLastModifiedTime = lastModified;
192 reload = true;
193 }
194 }
195 }
196 }
197
198 /**
199 * Compile (if needed) and load a tag file
200 */
201 public Class loadTagFile() throws JasperException {
202
203 try {
204 if (ctxt.isRemoved()) {
205 throw new FileNotFoundException(jspUri);
206 }
207 if (options.getDevelopment() || firstTime ) {
208 synchronized (this) {
209 firstTime = false;
210 ctxt.compile();
211 }
212 } else {
213 if (compileException != null) {
214 throw compileException;
215 }
216 }
217
218 if (reload) {
219 tagHandlerClass = ctxt.load();
220 }
221 } catch (FileNotFoundException ex) {
222 throw new JasperException(ex);
223 }
224
225 return tagHandlerClass;
226 }
227
228 /**
229 * Compile and load a prototype for the Tag file. This is needed
230 * when compiling tag files with circular dependencies. A prototpe
231 * (skeleton) with no dependencies on other other tag files is
232 * generated and compiled.
233 */
234 public Class loadTagFilePrototype() throws JasperException {
235
236 ctxt.setPrototypeMode(true);
237 try {
238 return loadTagFile();
239 } finally {
240 ctxt.setPrototypeMode(false);
241 }
242 }
243
244 /**
245 * Get a list of files that the current page has source dependency on.
246 */
247 public java.util.List getDependants() {
248 try {
249 Object target;
250 if (isTagFile) {
251 if (reload) {
252 tagHandlerClass = ctxt.load();
253 }
254 target = tagHandlerClass.newInstance();
255 } else {
256 target = getServlet();
257 }
258 if (target != null && target instanceof JspSourceDependent) {
259 return ((java.util.List) ((JspSourceDependent) target).getDependants());
260 }
261 } catch (Throwable ex) {
262 }
263 return null;
264 }
265
266 public boolean isTagFile() {
267 return this.isTagFile;
268 }
269
270 public int incTripCount() {
271 return tripCount++;
272 }
273
274 public int decTripCount() {
275 return tripCount--;
276 }
277
278 public void service(HttpServletRequest request,
279 HttpServletResponse response,
280 boolean precompile)
281 throws ServletException, IOException, FileNotFoundException {
282 try {
283
284 if (ctxt.isRemoved()) {
285 throw new FileNotFoundException(jspUri);
286 }
287
288 if ((available > 0L) && (available < Long.MAX_VALUE)) {
289 response.setDateHeader("Retry-After", available);
290 response.sendError
291 (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
292 Localizer.getMessage("jsp.error.unavailable"));
293 }
294
295 /*
296 * (1) Compile
297 */
298 if (options.getDevelopment() || firstTime ) {
299 synchronized (this) {
300 firstTime = false;
301
302 // The following sets reload to true, if necessary
303 ctxt.compile();
304 }
305 } else {
306 if (compileException != null) {
307 // Throw cached compilation exception
308 throw compileException;
309 }
310 }
311
312 /*
313 * (2) (Re)load servlet class file
314 */
315 getServlet();
316
317 // If a page is to be precompiled only, return.
318 if (precompile) {
319 return;
320 }
321
322 /*
323 * (3) Service request
324 */
325 if (theServlet instanceof SingleThreadModel) {
326 // sync on the wrapper so that the freshness
327 // of the page is determined right before servicing
328 synchronized (this) {
329 theServlet.service(request, response);
330 }
331 } else {
332 theServlet.service(request, response);
333 }
334
335 } catch (UnavailableException ex) {
336 String includeRequestUri = (String)
337 request.getAttribute("javax.servlet.include.request_uri");
338 if (includeRequestUri != null) {
339 // This file was included. Throw an exception as
340 // a response.sendError() will be ignored by the
341 // servlet engine.
342 throw ex;
343 } else {
344 int unavailableSeconds = ex.getUnavailableSeconds();
345 if (unavailableSeconds <= 0) {
346 unavailableSeconds = 60; // Arbitrary default
347 }
348 available = System.currentTimeMillis() +
349 (unavailableSeconds * 1000L);
350 response.sendError
351 (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
352 ex.getMessage());
353 }
354 } catch (FileNotFoundException ex) {
355 ctxt.incrementRemoved();
356 String includeRequestUri = (String)
357 request.getAttribute("javax.servlet.include.request_uri");
358 if (includeRequestUri != null) {
359 // This file was included. Throw an exception as
360 // a response.sendError() will be ignored by the
361 // servlet engine.
362 throw new ServletException(ex);
363 } else {
364 try {
365 response.sendError(HttpServletResponse.SC_NOT_FOUND,
366 ex.getMessage());
367 } catch (IllegalStateException ise) {
368 log.error(Localizer.getMessage("jsp.error.file.not.found",
369 ex.getMessage()),
370 ex);
371 }
372 }
373 } catch (ServletException ex) {
374 if(options.getDevelopment()) {
375 throw handleJspException(ex);
376 } else {
377 throw ex;
378 }
379 } catch (IOException ex) {
380 if(options.getDevelopment()) {
381 throw handleJspException(ex);
382 } else {
383 throw ex;
384 }
385 } catch (IllegalStateException ex) {
386 if(options.getDevelopment()) {
387 throw handleJspException(ex);
388 } else {
389 throw ex;
390 }
391 } catch (Exception ex) {
392 if(options.getDevelopment()) {
393 throw handleJspException(ex);
394 } else {
395 throw new JasperException(ex);
396 }
397 }
398 }
399
400 public void destroy() {
401 if (theServlet != null) {
402 theServlet.destroy();
403 }
404 }
405
406 /**
407 * @return Returns the lastModificationTest.
408 */
409 public long getLastModificationTest() {
410 return lastModificationTest;
411 }
412 /**
413 * @param lastModificationTest The lastModificationTest to set.
414 */
415 public void setLastModificationTest(long lastModificationTest) {
416 this.lastModificationTest = lastModificationTest;
417 }
418
419 /**
420 * <p>Attempts to construct a JasperException that contains helpful information
421 * about what went wrong. Uses the JSP compiler system to translate the line
422 * number in the generated servlet that originated the exception to a line
423 * number in the JSP. Then constructs an exception containing that
424 * information, and a snippet of the JSP to help debugging.
425 * Please see http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and
426 * http://www.tfenne.com/jasper/ for more details.
427 *</p>
428 *
429 * @param ex the exception that was the cause of the problem.
430 * @return a JasperException with more detailed information
431 */
432 protected JasperException handleJspException(Exception ex) {
433 try {
434 Throwable realException = ex;
435 if (ex instanceof ServletException) {
436 realException = ((ServletException) ex).getRootCause();
437 }
438
439 // First identify the stack frame in the trace that represents the JSP
440 StackTraceElement[] frames = realException.getStackTrace();
441 StackTraceElement jspFrame = null;
442
443 for (int i=0; i<frames.length; ++i) {
444 if ( frames[i].getClassName().equals(this.getServlet().getClass().getName()) ) {
445 jspFrame = frames[i];
446 break;
447 }
448 }
449
450 if (jspFrame == null) {
451 // If we couldn't find a frame in the stack trace corresponding
452 // to the generated servlet class, we can't really add anything
453 return new JasperException(ex);
454 }
455 else {
456 int javaLineNumber = jspFrame.getLineNumber();
457 JavacErrorDetail detail = ErrorDispatcher.createJavacError(
458 jspFrame.getMethodName(),
459 this.ctxt.getCompiler().getPageNodes(),
460 null,
461 javaLineNumber);
462
463 // If the line number is less than one we couldn't find out
464 // where in the JSP things went wrong
465 int jspLineNumber = detail.getJspBeginLineNumber();
466 if (jspLineNumber < 1) {
467 throw new JasperException(ex);
468 }
469
470 // Read both files in, so we can inspect them
471 String[] jspLines = readFile
472 (this.ctxt.getResourceAsStream(this.ctxt.getJspFile()));
473
474 String[] javaLines = readFile
475 (new FileInputStream(this.ctxt.getServletJavaFileName()));
476
477 // If the line contains the opening of a multi-line scriptlet
478 // block, then the JSP line number we got back is probably
479 // faulty. Scan forward to match the java line...
480 if (jspLines[jspLineNumber-1].lastIndexOf("<%") >
481 jspLines[jspLineNumber-1].lastIndexOf("%>")) {
482 String javaLine = javaLines[javaLineNumber-1].trim();
483
484 for (int i=jspLineNumber-1; i<jspLines.length; i++) {
485 if (jspLines[i].indexOf(javaLine) != -1) {
486 jspLineNumber = i+1;
487 break;
488 }
489 }
490 }
491
492 // copy out a fragment of JSP to display to the user
493 StringBuffer buffer = new StringBuffer(1024);
494 int startIndex = Math.max(0, jspLineNumber-1-3);
495 int endIndex = Math.min(jspLines.length-1, jspLineNumber-1+3);
496
497 for (int i=startIndex;i<=endIndex; ++i) {
498 buffer.append(i+1);
499 buffer.append(": ");
500 buffer.append(jspLines[i]);
501 buffer.append("\n");
502 }
503
504 return new JasperException(
505 "Exception in JSP: " + detail.getJspFileName() + ":" +
506 jspLineNumber + "\n\n" + buffer + "\n\nStacktrace:", ex);
507 }
508 } catch (Exception je) {
509 // If anything goes wrong, just revert to the original behaviour
510 return new JasperException(ex);
511 }
512 }
513
514 /**
515 * Reads a text file from an input stream into a String[]. Used to read in
516 * the JSP and generated Java file when generating error messages.
517 */
518 private String[] readFile(InputStream s) throws IOException {
519 BufferedReader reader = new BufferedReader(new InputStreamReader(s));
520 List lines = new ArrayList();
521 String line;
522
523 while ( (line = reader.readLine()) != null ) {
524 lines.add(line);
525 }
526
527 return (String[]) lines.toArray( new String[lines.size()] );
528 }
529
530 }