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