001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jasper.servlet;
019:
020: import java.io.FileNotFoundException;
021: import java.io.IOException;
022: import java.net.URL;
023:
024: import javax.servlet.Servlet;
025: import javax.servlet.ServletConfig;
026: import javax.servlet.ServletContext;
027: import javax.servlet.ServletException;
028: import javax.servlet.SingleThreadModel;
029: import javax.servlet.UnavailableException;
030: import javax.servlet.http.HttpServletRequest;
031: import javax.servlet.http.HttpServletResponse;
032: import javax.servlet.jsp.tagext.TagInfo;
033:
034: import org.apache.AnnotationProcessor;
035: import org.apache.jasper.JasperException;
036: import org.apache.jasper.JspCompilationContext;
037: import org.apache.jasper.Options;
038: import org.apache.jasper.compiler.ErrorDispatcher;
039: import org.apache.jasper.compiler.JavacErrorDetail;
040: import org.apache.jasper.compiler.JspRuntimeContext;
041: import org.apache.jasper.compiler.Localizer;
042: import org.apache.jasper.runtime.JspSourceDependent;
043: import org.apache.juli.logging.Log;
044: import org.apache.juli.logging.LogFactory;
045:
046: /**
047: * The JSP engine (a.k.a Jasper).
048: *
049: * The servlet container is responsible for providing a
050: * URLClassLoader for the web application context Jasper
051: * is being used in. Jasper will try get the Tomcat
052: * ServletContext attribute for its ServletContext class
053: * loader, if that fails, it uses the parent class loader.
054: * In either case, it must be a URLClassLoader.
055: *
056: * @author Anil K. Vijendran
057: * @author Harish Prabandham
058: * @author Remy Maucherat
059: * @author Kin-man Chung
060: * @author Glenn Nielsen
061: * @author Tim Fennell
062: */
063:
064: public class JspServletWrapper {
065:
066: // Logger
067: private Log log = LogFactory.getLog(JspServletWrapper.class);
068:
069: private Servlet theServlet;
070: private String jspUri;
071: private Class servletClass;
072: private Class tagHandlerClass;
073: private JspCompilationContext ctxt;
074: private long available = 0L;
075: private ServletConfig config;
076: private Options options;
077: private boolean firstTime = true;
078: private boolean reload = true;
079: private boolean isTagFile;
080: private int tripCount;
081: private JasperException compileException;
082: private long servletClassLastModifiedTime;
083: private long lastModificationTest = 0L;
084:
085: /*
086: * JspServletWrapper for JSP pages.
087: */
088: public JspServletWrapper(ServletConfig config, Options options,
089: String jspUri, boolean isErrorPage, JspRuntimeContext rctxt)
090: throws JasperException {
091:
092: this .isTagFile = false;
093: this .config = config;
094: this .options = options;
095: this .jspUri = jspUri;
096: ctxt = new JspCompilationContext(jspUri, isErrorPage, options,
097: config.getServletContext(), this , rctxt);
098: }
099:
100: /*
101: * JspServletWrapper for tag files.
102: */
103: public JspServletWrapper(ServletContext servletContext,
104: Options options, String tagFilePath, TagInfo tagInfo,
105: JspRuntimeContext rctxt, URL tagFileJarUrl)
106: throws JasperException {
107:
108: this .isTagFile = true;
109: this .config = null; // not used
110: this .options = options;
111: this .jspUri = tagFilePath;
112: this .tripCount = 0;
113: ctxt = new JspCompilationContext(jspUri, tagInfo, options,
114: servletContext, this , rctxt, tagFileJarUrl);
115: }
116:
117: public JspCompilationContext getJspEngineContext() {
118: return ctxt;
119: }
120:
121: public void setReload(boolean reload) {
122: this .reload = reload;
123: }
124:
125: public Servlet getServlet() throws ServletException, IOException,
126: FileNotFoundException {
127: if (reload) {
128: synchronized (this ) {
129: // Synchronizing on jsw enables simultaneous loading
130: // of different pages, but not the same page.
131: if (reload) {
132: // This is to maintain the original protocol.
133: destroy();
134:
135: Servlet servlet = null;
136:
137: try {
138: servletClass = ctxt.load();
139: servlet = (Servlet) servletClass.newInstance();
140: AnnotationProcessor annotationProcessor = (AnnotationProcessor) config
141: .getServletContext().getAttribute(
142: AnnotationProcessor.class
143: .getName());
144: if (annotationProcessor != null) {
145: annotationProcessor
146: .processAnnotations(servlet);
147: annotationProcessor.postConstruct(servlet);
148: }
149: } catch (IllegalAccessException e) {
150: throw new JasperException(e);
151: } catch (InstantiationException e) {
152: throw new JasperException(e);
153: } catch (Exception e) {
154: throw new JasperException(e);
155: }
156:
157: servlet.init(config);
158:
159: if (!firstTime) {
160: ctxt.getRuntimeContext()
161: .incrementJspReloadCount();
162: }
163:
164: theServlet = servlet;
165: reload = false;
166: }
167: }
168: }
169: return theServlet;
170: }
171:
172: public ServletContext getServletContext() {
173: return config.getServletContext();
174: }
175:
176: /**
177: * Sets the compilation exception for this JspServletWrapper.
178: *
179: * @param je The compilation exception
180: */
181: public void setCompilationException(JasperException je) {
182: this .compileException = je;
183: }
184:
185: /**
186: * Sets the last-modified time of the servlet class file associated with
187: * this JspServletWrapper.
188: *
189: * @param lastModified Last-modified time of servlet class
190: */
191: public void setServletClassLastModifiedTime(long lastModified) {
192: if (this .servletClassLastModifiedTime < lastModified) {
193: synchronized (this ) {
194: if (this .servletClassLastModifiedTime < lastModified) {
195: this .servletClassLastModifiedTime = lastModified;
196: reload = true;
197: }
198: }
199: }
200: }
201:
202: /**
203: * Compile (if needed) and load a tag file
204: */
205: public Class loadTagFile() throws JasperException {
206:
207: try {
208: if (ctxt.isRemoved()) {
209: throw new FileNotFoundException(jspUri);
210: }
211: if (options.getDevelopment() || firstTime) {
212: synchronized (this ) {
213: firstTime = false;
214: ctxt.compile();
215: }
216: } else {
217: if (compileException != null) {
218: throw compileException;
219: }
220: }
221:
222: if (reload) {
223: tagHandlerClass = ctxt.load();
224: reload = false;
225: }
226: } catch (FileNotFoundException ex) {
227: throw new JasperException(ex);
228: }
229:
230: return tagHandlerClass;
231: }
232:
233: /**
234: * Compile and load a prototype for the Tag file. This is needed
235: * when compiling tag files with circular dependencies. A prototpe
236: * (skeleton) with no dependencies on other other tag files is
237: * generated and compiled.
238: */
239: public Class loadTagFilePrototype() throws JasperException {
240:
241: ctxt.setPrototypeMode(true);
242: try {
243: return loadTagFile();
244: } finally {
245: ctxt.setPrototypeMode(false);
246: }
247: }
248:
249: /**
250: * Get a list of files that the current page has source dependency on.
251: */
252: public java.util.List getDependants() {
253: try {
254: Object target;
255: if (isTagFile) {
256: if (reload) {
257: tagHandlerClass = ctxt.load();
258: reload = false;
259: }
260: target = tagHandlerClass.newInstance();
261: } else {
262: target = getServlet();
263: }
264: if (target != null && target instanceof JspSourceDependent) {
265: return ((java.util.List) ((JspSourceDependent) target)
266: .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, boolean precompile)
287: throws ServletException, IOException, FileNotFoundException {
288:
289: try {
290:
291: if (ctxt.isRemoved()) {
292: throw new FileNotFoundException(jspUri);
293: }
294:
295: if ((available > 0L) && (available < Long.MAX_VALUE)) {
296: if (available > System.currentTimeMillis()) {
297: response.setDateHeader("Retry-After", available);
298: response
299: .sendError(
300: HttpServletResponse.SC_SERVICE_UNAVAILABLE,
301: Localizer
302: .getMessage("jsp.error.unavailable"));
303: return;
304: } else {
305: // Wait period has expired. Reset.
306: available = 0;
307: }
308: }
309:
310: /*
311: * (1) Compile
312: */
313: if (options.getDevelopment() || firstTime) {
314: synchronized (this ) {
315: firstTime = false;
316:
317: // The following sets reload to true, if necessary
318: ctxt.compile();
319: }
320: } else {
321: if (compileException != null) {
322: // Throw cached compilation exception
323: throw compileException;
324: }
325: }
326:
327: /*
328: * (2) (Re)load servlet class file
329: */
330: getServlet();
331:
332: // If a page is to be precompiled only, return.
333: if (precompile) {
334: return;
335: }
336:
337: } catch (FileNotFoundException ex) {
338: ctxt.incrementRemoved();
339: String includeRequestUri = (String) request
340: .getAttribute("javax.servlet.include.request_uri");
341: if (includeRequestUri != null) {
342: // This file was included. Throw an exception as
343: // a response.sendError() will be ignored by the
344: // servlet engine.
345: throw new ServletException(ex);
346: } else {
347: try {
348: response.sendError(
349: HttpServletResponse.SC_NOT_FOUND, ex
350: .getMessage());
351: } catch (IllegalStateException ise) {
352: log.error(Localizer
353: .getMessage("jsp.error.file.not.found", ex
354: .getMessage()), ex);
355: }
356: }
357: } catch (ServletException ex) {
358: if (options.getDevelopment()) {
359: throw handleJspException(ex);
360: } else {
361: throw ex;
362: }
363: } catch (IOException ex) {
364: if (options.getDevelopment()) {
365: throw handleJspException(ex);
366: } else {
367: throw ex;
368: }
369: } catch (IllegalStateException ex) {
370: if (options.getDevelopment()) {
371: throw handleJspException(ex);
372: } else {
373: throw ex;
374: }
375: } catch (Exception ex) {
376: if (options.getDevelopment()) {
377: throw handleJspException(ex);
378: } else {
379: throw new JasperException(ex);
380: }
381: }
382:
383: try {
384:
385: /*
386: * (3) Service request
387: */
388: if (theServlet instanceof SingleThreadModel) {
389: // sync on the wrapper so that the freshness
390: // of the page is determined right before servicing
391: synchronized (this ) {
392: theServlet.service(request, response);
393: }
394: } else {
395: theServlet.service(request, response);
396: }
397:
398: } catch (UnavailableException ex) {
399: String includeRequestUri = (String) request
400: .getAttribute("javax.servlet.include.request_uri");
401: if (includeRequestUri != null) {
402: // This file was included. Throw an exception as
403: // a response.sendError() will be ignored by the
404: // servlet engine.
405: throw ex;
406: } else {
407: int unavailableSeconds = ex.getUnavailableSeconds();
408: if (unavailableSeconds <= 0) {
409: unavailableSeconds = 60; // Arbitrary default
410: }
411: available = System.currentTimeMillis()
412: + (unavailableSeconds * 1000L);
413: response.sendError(
414: HttpServletResponse.SC_SERVICE_UNAVAILABLE, ex
415: .getMessage());
416: }
417: } catch (ServletException ex) {
418: if (options.getDevelopment()) {
419: throw handleJspException(ex);
420: } else {
421: throw ex;
422: }
423: } catch (IOException ex) {
424: if (options.getDevelopment()) {
425: throw handleJspException(ex);
426: } else {
427: throw ex;
428: }
429: } catch (IllegalStateException ex) {
430: if (options.getDevelopment()) {
431: throw handleJspException(ex);
432: } else {
433: throw ex;
434: }
435: } catch (Exception ex) {
436: if (options.getDevelopment()) {
437: throw handleJspException(ex);
438: } else {
439: throw new JasperException(ex);
440: }
441: }
442: }
443:
444: public void destroy() {
445: if (theServlet != null) {
446: theServlet.destroy();
447: AnnotationProcessor annotationProcessor = (AnnotationProcessor) config
448: .getServletContext().getAttribute(
449: AnnotationProcessor.class.getName());
450: if (annotationProcessor != null) {
451: try {
452: annotationProcessor.preDestroy(theServlet);
453: } catch (Exception e) {
454: // Log any exception, since it can't be passed along
455: log.error(
456: Localizer.getMessage(
457: "jsp.error.file.not.found", e
458: .getMessage()), e);
459: }
460: }
461: }
462: }
463:
464: /**
465: * @return Returns the lastModificationTest.
466: */
467: public long getLastModificationTest() {
468: return lastModificationTest;
469: }
470:
471: /**
472: * @param lastModificationTest The lastModificationTest to set.
473: */
474: public void setLastModificationTest(long lastModificationTest) {
475: this .lastModificationTest = lastModificationTest;
476: }
477:
478: /**
479: * <p>Attempts to construct a JasperException that contains helpful information
480: * about what went wrong. Uses the JSP compiler system to translate the line
481: * number in the generated servlet that originated the exception to a line
482: * number in the JSP. Then constructs an exception containing that
483: * information, and a snippet of the JSP to help debugging.
484: * Please see http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and
485: * http://www.tfenne.com/jasper/ for more details.
486: *</p>
487: *
488: * @param ex the exception that was the cause of the problem.
489: * @return a JasperException with more detailed information
490: */
491: protected JasperException handleJspException(Exception ex) {
492: try {
493: Throwable realException = ex;
494: if (ex instanceof ServletException) {
495: realException = ((ServletException) ex).getRootCause();
496: }
497:
498: // First identify the stack frame in the trace that represents the JSP
499: StackTraceElement[] frames = realException.getStackTrace();
500: StackTraceElement jspFrame = null;
501:
502: for (int i = 0; i < frames.length; ++i) {
503: if (frames[i].getClassName().equals(
504: this .getServlet().getClass().getName())) {
505: jspFrame = frames[i];
506: break;
507: }
508: }
509:
510: if (jspFrame == null) {
511: // If we couldn't find a frame in the stack trace corresponding
512: // to the generated servlet class, we can't really add anything
513: return new JasperException(ex);
514: } else {
515: int javaLineNumber = jspFrame.getLineNumber();
516: JavacErrorDetail detail = ErrorDispatcher
517: .createJavacError(jspFrame.getMethodName(),
518: this .ctxt.getCompiler().getPageNodes(),
519: null, javaLineNumber, ctxt);
520:
521: // If the line number is less than one we couldn't find out
522: // where in the JSP things went wrong
523: int jspLineNumber = detail.getJspBeginLineNumber();
524: if (jspLineNumber < 1) {
525: throw new JasperException(ex);
526: }
527:
528: if (options.getDisplaySourceFragment()) {
529: return new JasperException(Localizer.getMessage(
530: "jsp.exception", detail.getJspFileName(),
531: "" + jspLineNumber)
532: + "\n\n"
533: + detail.getJspExtract()
534: + "\n\nStacktrace:", ex);
535:
536: } else {
537: return new JasperException(Localizer.getMessage(
538: "jsp.exception", detail.getJspFileName(),
539: "" + jspLineNumber), ex);
540: }
541: }
542: } catch (Exception je) {
543: // If anything goes wrong, just revert to the original behaviour
544: if (ex instanceof JasperException) {
545: return (JasperException) ex;
546: } else {
547: return new JasperException(ex);
548: }
549: }
550: }
551:
552: }
|