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 ctxt.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 (FileNotFoundException fnfe) {
343 // File has been removed. Let caller handle this.
344 throw fnfe;
345 } catch (IOException ex) {
346 if (options.getDevelopment()) {
347 throw handleJspException(ex);
348 } else {
349 throw ex;
350 }
351 } catch (IllegalStateException ex) {
352 if (options.getDevelopment()) {
353 throw handleJspException(ex);
354 } else {
355 throw ex;
356 }
357 } catch (Exception ex) {
358 if (options.getDevelopment()) {
359 throw handleJspException(ex);
360 } else {
361 throw new JasperException(ex);
362 }
363 }
364
365 try {
366
367 /*
368 * (3) Service request
369 */
370 if (theServlet instanceof SingleThreadModel) {
371 // sync on the wrapper so that the freshness
372 // of the page is determined right before servicing
373 synchronized (this) {
374 theServlet.service(request, response);
375 }
376 } else {
377 theServlet.service(request, response);
378 }
379
380 } catch (UnavailableException ex) {
381 String includeRequestUri = (String)
382 request.getAttribute("javax.servlet.include.request_uri");
383 if (includeRequestUri != null) {
384 // This file was included. Throw an exception as
385 // a response.sendError() will be ignored by the
386 // servlet engine.
387 throw ex;
388 } else {
389 int unavailableSeconds = ex.getUnavailableSeconds();
390 if (unavailableSeconds <= 0) {
391 unavailableSeconds = 60; // Arbitrary default
392 }
393 available = System.currentTimeMillis() +
394 (unavailableSeconds * 1000L);
395 response.sendError
396 (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
397 ex.getMessage());
398 }
399 } catch (ServletException ex) {
400 if(options.getDevelopment()) {
401 throw handleJspException(ex);
402 } else {
403 throw ex;
404 }
405 } catch (IOException ex) {
406 if(options.getDevelopment()) {
407 throw handleJspException(ex);
408 } else {
409 throw ex;
410 }
411 } catch (IllegalStateException ex) {
412 if(options.getDevelopment()) {
413 throw handleJspException(ex);
414 } else {
415 throw ex;
416 }
417 } catch (Exception ex) {
418 if(options.getDevelopment()) {
419 throw handleJspException(ex);
420 } else {
421 throw new JasperException(ex);
422 }
423 }
424 }
425
426 public void destroy() {
427 if (theServlet != null) {
428 theServlet.destroy();
429 AnnotationProcessor annotationProcessor = (AnnotationProcessor) config.getServletContext().getAttribute(AnnotationProcessor.class.getName());
430 if (annotationProcessor != null) {
431 try {
432 annotationProcessor.preDestroy(theServlet);
433 } catch (Exception e) {
434 // Log any exception, since it can't be passed along
435 log.error(Localizer.getMessage("jsp.error.file.not.found",
436 e.getMessage()), e);
437 }
438 }
439 }
440 }
441
442 /**
443 * @return Returns the lastModificationTest.
444 */
445 public long getLastModificationTest() {
446 return lastModificationTest;
447 }
448 /**
449 * @param lastModificationTest The lastModificationTest to set.
450 */
451 public void setLastModificationTest(long lastModificationTest) {
452 this.lastModificationTest = lastModificationTest;
453 }
454
455 /**
456 * <p>Attempts to construct a JasperException that contains helpful information
457 * about what went wrong. Uses the JSP compiler system to translate the line
458 * number in the generated servlet that originated the exception to a line
459 * number in the JSP. Then constructs an exception containing that
460 * information, and a snippet of the JSP to help debugging.
461 * Please see http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and
462 * http://www.tfenne.com/jasper/ for more details.
463 *</p>
464 *
465 * @param ex the exception that was the cause of the problem.
466 * @return a JasperException with more detailed information
467 */
468 protected JasperException handleJspException(Exception ex) {
469 try {
470 Throwable realException = ex;
471 if (ex instanceof ServletException) {
472 realException = ((ServletException) ex).getRootCause();
473 }
474
475 // First identify the stack frame in the trace that represents the JSP
476 StackTraceElement[] frames = realException.getStackTrace();
477 StackTraceElement jspFrame = null;
478
479 for (int i=0; i<frames.length; ++i) {
480 if ( frames[i].getClassName().equals(this.getServlet().getClass().getName()) ) {
481 jspFrame = frames[i];
482 break;
483 }
484 }
485
486 if (jspFrame == null ||
487 this.ctxt.getCompiler().getPageNodes() == null) {
488 // If we couldn't find a frame in the stack trace corresponding
489 // to the generated servlet class or we don't have a copy of the
490 // parsed JSP to hand, we can't really add anything
491 return new JasperException(ex);
492 }
493 else {
494 int javaLineNumber = jspFrame.getLineNumber();
495 JavacErrorDetail detail = ErrorDispatcher.createJavacError(
496 jspFrame.getMethodName(),
497 this.ctxt.getCompiler().getPageNodes(),
498 null,
499 javaLineNumber,
500 ctxt);
501
502 // If the line number is less than one we couldn't find out
503 // where in the JSP things went wrong
504 int jspLineNumber = detail.getJspBeginLineNumber();
505 if (jspLineNumber < 1) {
506 throw new JasperException(ex);
507 }
508
509 if (options.getDisplaySourceFragment()) {
510 return new JasperException(Localizer.getMessage
511 ("jsp.exception", detail.getJspFileName(),
512 "" + jspLineNumber) +
513 "\n\n" + detail.getJspExtract() +
514 "\n\nStacktrace:", ex);
515
516 } else {
517 return new JasperException(Localizer.getMessage
518 ("jsp.exception", detail.getJspFileName(),
519 "" + jspLineNumber), ex);
520 }
521 }
522 } catch (Exception je) {
523 // If anything goes wrong, just revert to the original behaviour
524 if (ex instanceof JasperException) {
525 return (JasperException) ex;
526 } else {
527 return new JasperException(ex);
528 }
529 }
530 }
531
532 }