View Javadoc

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