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.util.HashMap;
23 import java.util.Map;
24
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.Servlet;
27 import javax.servlet.ServletConfig;
28 import javax.servlet.ServletContext;
29 import javax.servlet.ServletException;
30 import javax.servlet.SingleThreadModel;
31 import javax.servlet.UnavailableException;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34 import javax.servlet.jsp.tagext.TagInfo;
35
36 import org.apache.jasper.JasperException;
37 import org.apache.jasper.JspCompilationContext;
38 import org.apache.jasper.Options;
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.compiler.SmapInput;
43 import org.apache.jasper.compiler.SmapStratum;
44 import org.apache.jasper.runtime.ExceptionUtils;
45 import org.apache.jasper.runtime.InstanceManagerFactory;
46 import org.apache.jasper.runtime.JspSourceDependent;
47 import org.apache.jasper.util.FastRemovalDequeue;
48 import org.apache.juli.logging.Log;
49 import org.apache.juli.logging.LogFactory;
50 import org.apache.tomcat.InstanceManager;
51 import org.apache.tomcat.Jar;
52
53 /**
54  * The JSP engine (a.k.a Jasper).
55  *
56  * The servlet container is responsible for providing a
57  * URLClassLoader for the web application context Jasper
58  * is being used in. Jasper will try get the Tomcat
59  * ServletContext attribute for its ServletContext class
60  * loader, if that fails, it uses the parent class loader.
61  * In either case, it must be a URLClassLoader.
62  *
63  * @author Anil K. Vijendran
64  * @author Harish Prabandham
65  * @author Remy Maucherat
66  * @author Kin-man Chung
67  * @author Glenn Nielsen
68  * @author Tim Fennell
69  */

70
71 @SuppressWarnings("deprecation"// Have to support SingleThreadModel
72 public class JspServletWrapper {
73
74     private static final Map<String,Long> ALWAYS_OUTDATED_DEPENDENCIES =
75             new HashMap<>();
76
77     static {
78         // If this is missing,
79         ALWAYS_OUTDATED_DEPENDENCIES.put("/WEB-INF/web.xml", Long.valueOf(-1));
80     }
81
82     // Logger
83     private final Log log = LogFactory.getLog(JspServletWrapper.class); // must not be static
84
85     private volatile Servlet theServlet;
86     private final String jspUri;
87     private volatile Class<?> tagHandlerClass;
88     private final JspCompilationContext ctxt;
89     private long available = 0L;
90     private final ServletConfig config;
91     private final Options options;
92     /*
93      * The servlet / tag file needs a compilation check on first access. Use a
94      * separate flag (rather then theServlet == null / tagHandlerClass == null
95      * as it avoids the potentially expensive isOutDated() calls in
96      * ctxt.compile() if there are multiple concurrent requests for the servlet
97      * / tag before the class has been loaded.
98      */

99     private volatile boolean mustCompile = true;
100     /* Whether the servlet/tag file needs reloading on next access */
101     private volatile boolean reload = true;
102     private final boolean isTagFile;
103     private int tripCount;
104     private JasperException compileException;
105     /* Timestamp of last time servlet resource was modified */
106     private volatile long servletClassLastModifiedTime;
107     private long lastModificationTest = 0L;
108     private long lastUsageTime = System.currentTimeMillis();
109     private FastRemovalDequeue<JspServletWrapper>.Entry unloadHandle;
110     private final boolean unloadAllowed;
111     private final boolean unloadByCount;
112     private final boolean unloadByIdle;
113
114     /*
115      * JspServletWrapper for JSP pages.
116      */

117     public JspServletWrapper(ServletConfig config, Options options,
118             String jspUri, JspRuntimeContext rctxt) {
119
120         this.isTagFile = false;
121         this.config = config;
122         this.options = options;
123         this.jspUri = jspUri;
124         unloadByCount = options.getMaxLoadedJsps() > 0 ? true : false;
125         unloadByIdle = options.getJspIdleTimeout() > 0 ? true : false;
126         unloadAllowed = unloadByCount || unloadByIdle ? true : false;
127         ctxt = new JspCompilationContext(jspUri, options,
128                                          config.getServletContext(),
129                                          this, rctxt);
130     }
131
132     /*
133      * JspServletWrapper for tag files.
134      */

135     public JspServletWrapper(ServletContext servletContext,
136                              Options options,
137                              String tagFilePath,
138                              TagInfo tagInfo,
139                              JspRuntimeContext rctxt,
140                              Jar tagJar) {
141
142         this.isTagFile = true;
143         this.config = null;        // not used
144         this.options = options;
145         this.jspUri = tagFilePath;
146         this.tripCount = 0;
147         unloadByCount = options.getMaxLoadedJsps() > 0 ? true : false;
148         unloadByIdle = options.getJspIdleTimeout() > 0 ? true : false;
149         unloadAllowed = unloadByCount || unloadByIdle ? true : false;
150         ctxt = new JspCompilationContext(jspUri, tagInfo, options,
151                                          servletContext, this, rctxt,
152                                          tagJar);
153     }
154
155     public JspCompilationContext getJspEngineContext() {
156         return ctxt;
157     }
158
159     public void setReload(boolean reload) {
160         this.reload = reload;
161     }
162
163     public boolean getReload() {
164         return reload;
165     }
166
167     private boolean getReloadInternal() {
168         return reload && !ctxt.getRuntimeContext().isCompileCheckInProgress();
169     }
170
171     public Servlet getServlet() throws ServletException {
172         /*
173          * DCL on 'reload' requires that 'reload' be volatile
174          * (this also forces a read memory barrier, ensuring the new servlet
175          * object is read consistently).
176          *
177          * When running in non development mode with a checkInterval it is
178          * possible (see BZ 62603) for a race condition to cause failures
179          * if a Servlet or tag is reloaded while a compile check is running
180          */

181         if (getReloadInternal() || theServlet == null) {
182             synchronized (this) {
183                 // Synchronizing on jsw enables simultaneous loading
184                 // of different pages, but not the same page.
185                 if (getReloadInternal() || theServlet == null) {
186                     // This is to maintain the original protocol.
187                     destroy();
188
189                     final Servlet servlet;
190
191                     try {
192                         InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);
193                         servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader());
194                     } catch (Exception e) {
195                         Throwable t = ExceptionUtils
196                                 .unwrapInvocationTargetException(e);
197                         ExceptionUtils.handleThrowable(t);
198                         throw new JasperException(t);
199                     }
200
201                     servlet.init(config);
202
203                     if (theServlet != null) {
204                         ctxt.getRuntimeContext().incrementJspReloadCount();
205                     }
206
207                     theServlet = servlet;
208                     reload = false;
209                     // Volatile 'reload' forces in order write of 'theServlet' and new servlet object
210                 }
211             }
212         }
213         return theServlet;
214     }
215
216     public ServletContext getServletContext() {
217         return ctxt.getServletContext();
218     }
219
220     /**
221      * Sets the compilation exception for this JspServletWrapper.
222      *
223      * @param je The compilation exception
224      */

225     public void setCompilationException(JasperException je) {
226         this.compileException = je;
227     }
228
229     /**
230      * Sets the last-modified time of the servlet class file associated with
231      * this JspServletWrapper.
232      *
233      * @param lastModified Last-modified time of servlet class
234      */

235     public void setServletClassLastModifiedTime(long lastModified) {
236         // DCL requires servletClassLastModifiedTime be volatile
237         // to force read and write barriers on access/set
238         // (and to get atomic write of long)
239         if (this.servletClassLastModifiedTime < lastModified) {
240             synchronized (this) {
241                 if (this.servletClassLastModifiedTime < lastModified) {
242                     this.servletClassLastModifiedTime = lastModified;
243                     reload = true;
244                     // Really need to unload the old class but can't do that. Do
245                     // the next best thing which is throw away the JspLoader so
246                     // a new loader will be created which will load the new
247                     // class.
248                     // TODO Are there inefficiencies between reload and the
249                     //      isOutDated() check?
250                     ctxt.clearJspLoader();
251                 }
252             }
253         }
254     }
255
256     /**
257      * Compile (if needed) and load a tag file.
258      * @return the loaded class
259      * @throws JasperException Error compiling or loading tag file
260      */

261     public Class<?> loadTagFile() throws JasperException {
262
263         try {
264             if (ctxt.isRemoved()) {
265                 throw new FileNotFoundException(jspUri);
266             }
267             if (options.getDevelopment() || mustCompile) {
268                 synchronized (this) {
269                     if (options.getDevelopment() || mustCompile) {
270                         ctxt.compile();
271                         mustCompile = false;
272                     }
273                 }
274             } else {
275                 if (compileException != null) {
276                     throw compileException;
277                 }
278             }
279
280             if (getReloadInternal() || tagHandlerClass == null) {
281                 synchronized (this) {
282                     if (getReloadInternal() || tagHandlerClass == null) {
283                         tagHandlerClass = ctxt.load();
284                         // Volatile 'reload' forces in order write of 'tagHandlerClass'
285                         reload = false;
286                     }
287                 }
288             }
289         } catch (FileNotFoundException ex) {
290             throw new JasperException(ex);
291         }
292
293         return tagHandlerClass;
294     }
295
296     /**
297      * Compile and load a prototype for the Tag file.  This is needed
298      * when compiling tag files with circular dependencies.  A prototype
299      * (skeleton) with no dependencies on other other tag files is
300      * generated and compiled.
301      * @return the loaded class
302      * @throws JasperException Error compiling or loading tag file
303      */

304     public Class<?> loadTagFilePrototype() throws JasperException {
305
306         ctxt.setPrototypeMode(true);
307         try {
308             return loadTagFile();
309         } finally {
310             ctxt.setPrototypeMode(false);
311         }
312     }
313
314     /**
315      * Get a list of files that the current page has source dependency on.
316      * @return the map of dependent resources
317      */

318     public java.util.Map<String,Long> getDependants() {
319         try {
320             Object target;
321             if (isTagFile) {
322                 if (reload) {
323                     synchronized (this) {
324                         if (reload) {
325                             tagHandlerClass = ctxt.load();
326                             reload = false;
327                         }
328                     }
329                 }
330                 target = tagHandlerClass.getConstructor().newInstance();
331             } else {
332                 target = getServlet();
333             }
334             if (target instanceof JspSourceDependent) {
335                 return ((JspSourceDependent) target).getDependants();
336             }
337         } catch (AbstractMethodError ame) {
338             // Almost certainly a pre Tomcat 7.0.17 compiled JSP using the old
339             // version of the interface. Force a re-compile.
340             return ALWAYS_OUTDATED_DEPENDENCIES;
341         } catch (Throwable ex) {
342             ExceptionUtils.handleThrowable(ex);
343         }
344         return null;
345     }
346
347     public boolean isTagFile() {
348         return this.isTagFile;
349     }
350
351     public int incTripCount() {
352         return tripCount++;
353     }
354
355     public int decTripCount() {
356         return tripCount--;
357     }
358
359     public String getJspUri() {
360         return jspUri;
361     }
362
363     public FastRemovalDequeue<JspServletWrapper>.Entry getUnloadHandle() {
364         return unloadHandle;
365     }
366
367     public void service(HttpServletRequest request,
368                         HttpServletResponse response,
369                         boolean precompile)
370             throws ServletException, IOException, FileNotFoundException {
371
372         Servlet servlet;
373
374         try {
375
376             if (ctxt.isRemoved()) {
377                 throw new FileNotFoundException(jspUri);
378             }
379
380             if ((available > 0L) && (available < Long.MAX_VALUE)) {
381                 if (available > System.currentTimeMillis()) {
382                     response.setDateHeader("Retry-After", available);
383                     response.sendError
384                         (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
385                          Localizer.getMessage("jsp.error.unavailable"));
386                     return;
387                 }
388
389                 // Wait period has expired. Reset.
390                 available = 0;
391             }
392
393             /*
394              * (1) Compile
395              */

396             if (options.getDevelopment() || mustCompile) {
397                 synchronized (this) {
398                     if (options.getDevelopment() || mustCompile) {
399                         // The following sets reload to trueif necessary
400                         ctxt.compile();
401                         mustCompile = false;
402                     }
403                 }
404             } else {
405                 if (compileException != null) {
406                     // Throw cached compilation exception
407                     throw compileException;
408                 }
409             }
410
411             /*
412              * (2) (Re)load servlet class file
413              */

414             servlet = getServlet();
415
416             // If a page is to be precompiled only, return.
417             if (precompile) {
418                 return;
419             }
420
421         } catch (ServletException ex) {
422             if (options.getDevelopment()) {
423                 throw handleJspException(ex);
424             }
425             throw ex;
426         } catch (FileNotFoundException fnfe) {
427             // File has been removed. Let caller handle this.
428             throw fnfe;
429         } catch (IOException ex) {
430             if (options.getDevelopment()) {
431                 throw handleJspException(ex);
432             }
433             throw ex;
434         } catch (IllegalStateException ex) {
435             if (options.getDevelopment()) {
436                 throw handleJspException(ex);
437             }
438             throw ex;
439         } catch (Exception ex) {
440             if (options.getDevelopment()) {
441                 throw handleJspException(ex);
442             }
443             throw new JasperException(ex);
444         }
445
446         try {
447             /*
448              * (3) Handle limitation of number of loaded Jsps
449              */

450             if (unloadAllowed) {
451                 synchronized(this) {
452                     if (unloadByCount) {
453                         if (unloadHandle == null) {
454                             unloadHandle = ctxt.getRuntimeContext().push(this);
455                         } else if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) {
456                             ctxt.getRuntimeContext().makeYoungest(unloadHandle);
457                             lastUsageTime = System.currentTimeMillis();
458                         }
459                     } else {
460                         if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) {
461                             lastUsageTime = System.currentTimeMillis();
462                         }
463                     }
464                 }
465             }
466
467             /*
468              * (4) Service request
469              */

470             if (servlet instanceof SingleThreadModel) {
471                // sync on the wrapper so that the freshness
472                // of the page is determined right before servicing
473                synchronized (this) {
474                    servlet.service(request, response);
475                 }
476             } else {
477                 servlet.service(request, response);
478             }
479         } catch (UnavailableException ex) {
480             String includeRequestUri = (String)
481                 request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
482             if (includeRequestUri != null) {
483                 // This file was included. Throw an exception as
484                 // a response.sendError() will be ignored by the
485                 // servlet engine.
486                 throw ex;
487             }
488
489             int unavailableSeconds = ex.getUnavailableSeconds();
490             if (unavailableSeconds <= 0) {
491                 unavailableSeconds = 60;        // Arbitrary default
492             }
493             available = System.currentTimeMillis() +
494                 (unavailableSeconds * 1000L);
495             response.sendError
496                 (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
497                  ex.getMessage());
498         } catch (ServletException ex) {
499             if(options.getDevelopment()) {
500                 throw handleJspException(ex);
501             }
502             throw ex;
503         } catch (IOException ex) {
504             if (options.getDevelopment()) {
505                 throw new IOException(handleJspException(ex).getMessage(), ex);
506             }
507             throw ex;
508         } catch (IllegalStateException ex) {
509             if(options.getDevelopment()) {
510                 throw handleJspException(ex);
511             }
512             throw ex;
513         } catch (Exception ex) {
514             if(options.getDevelopment()) {
515                 throw handleJspException(ex);
516             }
517             throw new JasperException(ex);
518         }
519     }
520
521     public void destroy() {
522         if (theServlet != null) {
523             try {
524                 theServlet.destroy();
525             } catch (Throwable t) {
526                 ExceptionUtils.handleThrowable(t);
527                 log.error(Localizer.getMessage("jsp.error.servlet.destroy.failed"), t);
528             }
529             InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);
530             try {
531                 instanceManager.destroyInstance(theServlet);
532             } catch (Exception e) {
533                 Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
534                 ExceptionUtils.handleThrowable(t);
535                 // Log any exception, since it can't be passed along
536                 log.error(Localizer.getMessage("jsp.error.file.not.found",
537                         e.getMessage()), t);
538             }
539         }
540     }
541
542     /**
543      * @return Returns the lastModificationTest.
544      */

545     public long getLastModificationTest() {
546         return lastModificationTest;
547     }
548     /**
549      * @param lastModificationTest The lastModificationTest to set.
550      */

551     public void setLastModificationTest(long lastModificationTest) {
552         this.lastModificationTest = lastModificationTest;
553     }
554
555     /**
556      * @return the lastUsageTime.
557      */

558     public long getLastUsageTime() {
559         return lastUsageTime;
560     }
561
562     /**
563      * <p>Attempts to construct a JasperException that contains helpful information
564      * about what went wrong. Uses the JSP compiler system to translate the line
565      * number in the generated servlet that originated the exception to a line
566      * number in the JSP.  Then constructs an exception containing that
567      * information, and a snippet of the JSP to help debugging.
568      * Please see https://bz.apache.org/bugzilla/show_bug.cgi?id=37062 and
569      * http://www.tfenne.com/jasper/ for more details.
570      *</p>
571      *
572      * @param ex the exception that was the cause of the problem.
573      * @return a JasperException with more detailed information
574      */

575     protected JasperException handleJspException(Exception ex) {
576         try {
577             Throwable realException = ex;
578             if (ex instanceof ServletException) {
579                 realException = ((ServletException) ex).getRootCause();
580             }
581
582             // Find the first stack frame that represents code generated by
583             // Jasper
584             StackTraceElement[] frames = realException.getStackTrace();
585             StackTraceElement jspFrame = null;
586
587             String servletPackageName = ctxt.getBasePackageName();
588             for (StackTraceElement frame : frames) {
589                 if (frame.getClassName().startsWith(servletPackageName)) {
590                     jspFrame = frame;
591                     break;
592                 }
593             }
594
595             SmapStratum smap = null;
596
597             if (jspFrame != null) {
598                 smap = ctxt.getCompiler().getSmap(jspFrame.getClassName());
599             }
600
601             if (smap == null) {
602                 // If we couldn't find a frame in the stack trace corresponding
603                 // to the generated servlet class or we don't have a copy of the
604                 // smap to hand, we can't really add anything
605                 return new JasperException(ex);
606             }
607
608             @SuppressWarnings("null")
609             int javaLineNumber = jspFrame.getLineNumber();
610             SmapInput source = smap.getInputLineNumber(javaLineNumber);
611
612             // If the line number is less than one we couldn't find out
613             // where in the JSP things went wrong
614             if (source.getLineNumber() < 1) {
615                 throw new JasperException(ex);
616             }
617
618             JavacErrorDetail detail = new JavacErrorDetail(jspFrame.getMethodName(), javaLineNumber,
619                     source.getFileName(), source.getLineNumber(), null, ctxt);
620
621             if (options.getDisplaySourceFragment()) {
622                 return new JasperException(Localizer.getMessage
623                         ("jsp.exception", detail.getJspFileName(),
624                                 "" + source.getLineNumber()) + System.lineSeparator() +
625                                 System.lineSeparator() + detail.getJspExtract() +
626                                 System.lineSeparator() + System.lineSeparator() +
627                                 "Stacktrace:", ex);
628
629             }
630
631             return new JasperException(Localizer.getMessage
632                     ("jsp.exception", detail.getJspFileName(),
633                             "" + source.getLineNumber()), ex);
634         } catch (Exception je) {
635             // If anything goes wrong, just revert to the original behaviour
636             if (ex instanceof JasperException) {
637                 return (JasperException) ex;
638             }
639             return new JasperException(ex);
640         }
641     }
642 }
643