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