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 com.opensymphony.xwork2.util.logging.Logger;
21  import com.opensymphony.xwork2.util.logging.LoggerFactory;
22  import org.apache.struts2.jasper.Constants;
23  import org.apache.struts2.jasper.EmbeddedServletOptions;
24  import org.apache.struts2.jasper.Options;
25  import org.apache.struts2.jasper.compiler.JspRuntimeContext;
26  import org.apache.struts2.jasper.compiler.Localizer;
27  
28  import javax.servlet.ServletConfig;
29  import javax.servlet.ServletContext;
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServlet;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  import java.io.IOException;
35  import java.lang.reflect.Constructor;
36  import java.util.Enumeration;
37  
38  /***
39   * The JSP engine (a.k.a Jasper).
40   * <p/>
41   * The servlet container is responsible for providing a
42   * URLClassLoader for the web application context Jasper
43   * is being used in. Jasper will try get the Tomcat
44   * ServletContext attribute for its ServletContext class
45   * loader, if that fails, it uses the parent class loader.
46   * In either case, it must be a URLClassLoader.
47   *
48   * @author Anil K. Vijendran
49   * @author Harish Prabandham
50   * @author Remy Maucherat
51   * @author Kin-man Chung
52   * @author Glenn Nielsen
53   */
54  public class JspServlet extends HttpServlet {
55  
56      // Logger
57      private Logger log = LoggerFactory.getLogger(JspServlet.class);
58  
59      private ServletContext context;
60      private ServletConfig config;
61      private Options options;
62      private JspRuntimeContext rctxt;
63  
64  
65      /*
66       * Initializes this JspServlet.
67       */
68      public void init(ServletConfig config) throws ServletException {
69  
70          super.init(config);
71          this.config = config;
72          this.context = config.getServletContext();
73  
74          // Initialize the JSP Runtime Context
75          // Check for a custom Options implementation
76          String engineOptionsName =
77                  config.getInitParameter("engineOptionsClass");
78          if (engineOptionsName != null) {
79              // Instantiate the indicated Options implementation
80              try {
81                  ClassLoader loader = Thread.currentThread()
82                          .getContextClassLoader();
83                  Class engineOptionsClass = loader.loadClass(engineOptionsName);
84                  Class[] ctorSig = {ServletConfig.class, ServletContext.class};
85                  Constructor ctor = engineOptionsClass.getConstructor(ctorSig);
86                  Object[] args = {config, context};
87                  options = (Options) ctor.newInstance(args);
88              } catch (Throwable e) {
89                  // Need to localize this.
90                  log.warn("Failed to load engineOptionsClass", e);
91                  // Use the default Options implementation
92                  options = new EmbeddedServletOptions(config, context);
93              }
94          } else {
95              // Use the default Options implementation
96              options = new EmbeddedServletOptions(config, context);
97          }
98          rctxt = new JspRuntimeContext(context, options);
99  
100         if (log.isDebugEnabled()) {
101             log.debug(Localizer.getMessage("jsp.message.scratch.dir.is",
102                     options.getScratchDir().toString()));
103             log.debug(Localizer.getMessage("jsp.message.dont.modify.servlets"));
104         }
105     }
106 
107 
108     /***
109      * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
110      * the number of JSPs that have been loaded into the webapp with which
111      * this JspServlet is associated.
112      * <p/>
113      * <p>This info may be used for monitoring purposes.
114      *
115      * @return The number of JSPs that have been loaded into the webapp with
116      *         which this JspServlet is associated
117      */
118     public int getJspCount() {
119         return this.rctxt.getJspCount();
120     }
121 
122 
123     /***
124      * Resets the JSP reload counter.
125      *
126      * @param count Value to which to reset the JSP reload counter
127      */
128     public void setJspReloadCount(int count) {
129         this.rctxt.setJspReloadCount(count);
130     }
131 
132 
133     /***
134      * Gets the number of JSPs that have been reloaded.
135      * <p/>
136      * <p>This info may be used for monitoring purposes.
137      *
138      * @return The number of JSPs (in the webapp with which this JspServlet is
139      *         associated) that have been reloaded
140      */
141     public int getJspReloadCount() {
142         return this.rctxt.getJspReloadCount();
143     }
144 
145 
146     /***
147      * <p>Look for a <em>precompilation request</em> as described in
148      * Section 8.4.2 of the JSP 1.2 Specification.  <strong>WARNING</strong> -
149      * we cannot use <code>request.getParameter()</code> for this, because
150      * that will trigger parsing all of the request parameters, and not give
151      * a servlet the opportunity to call
152      * <code>request.setCharacterEncoding()</code> first.</p>
153      *
154      * @param request The servlet requset we are processing
155      * @throws ServletException if an invalid parameter value for the
156      *                          <code>jsp_precompile</code> parameter name is specified
157      */
158     boolean preCompile(HttpServletRequest request) throws ServletException {
159 
160         String queryString = request.getQueryString();
161         if (queryString == null) {
162             return (false);
163         }
164         int start = queryString.indexOf(Constants.PRECOMPILE);
165         if (start < 0) {
166             return (false);
167         }
168         queryString =
169                 queryString.substring(start + Constants.PRECOMPILE.length());
170         if (queryString.length() == 0) {
171             return (true);             // ?jsp_precompile
172         }
173         if (queryString.startsWith("&")) {
174             return (true);             // ?jsp_precompile&foo=bar...
175         }
176         if (!queryString.startsWith("=")) {
177             return (false);            // part of some other name or value
178         }
179         int limit = queryString.length();
180         int ampersand = queryString.indexOf("&");
181         if (ampersand > 0) {
182             limit = ampersand;
183         }
184         String value = queryString.substring(1, limit);
185         if (value.equals("true")) {
186             return (true);             // ?jsp_precompile=true
187         } else if (value.equals("false")) {
188             // Spec says if jsp_precompile=false, the request should not
189             // be delivered to the JSP page; the easiest way to implement
190             // this is to set the flag to true, and precompile the page anyway.
191             // This still conforms to the spec, since it says the
192             // precompilation request can be ignored.
193             return (true);             // ?jsp_precompile=false
194         } else {
195             throw new ServletException("Cannot have request parameter " +
196                     Constants.PRECOMPILE + " set to " +
197                     value);
198         }
199 
200     }
201 
202 
203     public void service(HttpServletRequest request,
204                         HttpServletResponse response)
205             throws ServletException, IOException {
206 
207         String jspUri = null;
208 
209         String jspFile = (String) request.getAttribute(Constants.JSP_FILE);
210         if (jspFile != null) {
211             // JSP is specified via <jsp-file> in <servlet> declaration
212             jspUri = jspFile;
213         } else {
214             /*
215              * Check to see if the requested JSP has been the target of a
216              * RequestDispatcher.include()
217              */
218             jspUri = (String) request.getAttribute(Constants.INC_SERVLET_PATH);
219             if (jspUri != null) {
220                 /*
221                  * Requested JSP has been target of
222                  * RequestDispatcher.include(). Its path is assembled from the
223                  * relevant javax.servlet.include.* request attributes
224                  */
225                 String pathInfo = (String) request.getAttribute(
226                         "javax.servlet.include.path_info");
227                 if (pathInfo != null) {
228                     jspUri += pathInfo;
229                 }
230             } else {
231                 /*
232                  * Requested JSP has not been the target of a 
233                  * RequestDispatcher.include(). Reconstruct its path from the
234                  * request's getServletPath() and getPathInfo()
235                  */
236                 jspUri = request.getServletPath();
237                 String pathInfo = request.getPathInfo();
238                 if (pathInfo != null) {
239                     jspUri += pathInfo;
240                 }
241             }
242         }
243 
244         if (log.isDebugEnabled()) {
245             log.debug("JspEngine --> " + jspUri);
246             log.debug("\t     ServletPath: " + request.getServletPath());
247             log.debug("\t        PathInfo: " + request.getPathInfo());
248             log.debug("\t        RealPath: " + context.getRealPath(jspUri));
249             log.debug("\t      RequestURI: " + request.getRequestURI());
250             log.debug("\t     QueryString: " + request.getQueryString());
251             log.debug("\t  Request Params: ");
252             Enumeration e = request.getParameterNames();
253             while (e.hasMoreElements()) {
254                 String name = (String) e.nextElement();
255                 log.debug("\t\t " + name + " = "
256                         + request.getParameter(name));
257             }
258         }
259 
260         try {
261             boolean precompile = preCompile(request);
262             serviceJspFile(request, response, jspUri, null, precompile);
263         } catch (RuntimeException e) {
264             throw e;
265         } catch (ServletException e) {
266             throw e;
267         } catch (IOException e) {
268             throw e;
269         } catch (Throwable e) {
270             throw new ServletException(e);
271         }
272 
273     }
274 
275     public void destroy() {
276         if (log.isDebugEnabled()) {
277             log.debug("JspServlet.destroy()");
278         }
279 
280         rctxt.destroy();
281     }
282 
283 
284     // -------------------------------------------------------- Private Methods
285 
286     private void serviceJspFile(HttpServletRequest request,
287                                 HttpServletResponse response, String jspUri,
288                                 Throwable exception, boolean precompile)
289             throws ServletException, IOException {
290 
291         JspServletWrapper wrapper =
292                 (JspServletWrapper) rctxt.getWrapper(jspUri);
293         if (wrapper == null) {
294             synchronized (this) {
295                 wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri);
296                 if (wrapper == null) {
297                     // Check if the requested JSP page exists, to avoid
298                     // creating unnecessary directories and files.
299                     if (null == context.getResource(jspUri)) {
300                         String includeRequestUri = (String)
301                                 request.getAttribute("javax.servlet.include.request_uri");
302                         if (includeRequestUri != null) {
303                             // This file was included. Throw an exception as
304                             // a response.sendError() will be ignored
305                             throw new ServletException(Localizer.getMessage(
306                                     "jsp.error.file.not.found", jspUri));
307                         } else {
308                             try {
309                                 response.sendError(HttpServletResponse.SC_NOT_FOUND,
310                                         request.getRequestURI());
311                             } catch (IllegalStateException ise) {
312                                 log.error(Localizer.getMessage("jsp.error.file.not.found",
313                                         jspUri));
314                             }
315                         }
316                         return;
317                     }
318                     boolean isErrorPage = exception != null;
319                     wrapper = new JspServletWrapper(config, options, jspUri,
320                             isErrorPage, rctxt);
321                     rctxt.addWrapper(jspUri, wrapper);
322                 }
323             }
324         }
325 
326         wrapper.service(request, response, precompile);
327 
328     }
329 
330 }