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.catalina.core;
019:
020: import java.io.IOException;
021:
022: import javax.servlet.RequestDispatcher;
023: import javax.servlet.ServletContext;
024: import javax.servlet.ServletException;
025: import javax.servlet.http.HttpServletResponse;
026:
027: import org.apache.catalina.CometEvent;
028: import org.apache.catalina.Context;
029: import org.apache.catalina.Globals;
030: import org.apache.catalina.Wrapper;
031: import org.apache.catalina.connector.ClientAbortException;
032: import org.apache.catalina.connector.Request;
033: import org.apache.catalina.connector.Response;
034: import org.apache.catalina.deploy.ErrorPage;
035: import org.apache.catalina.util.RequestUtil;
036: import org.apache.catalina.util.StringManager;
037: import org.apache.catalina.valves.ValveBase;
038: import org.apache.juli.logging.Log;
039: import org.apache.juli.logging.LogFactory;
040:
041: /**
042: * Valve that implements the default basic behavior for the
043: * <code>StandardHost</code> container implementation.
044: * <p>
045: * <b>USAGE CONSTRAINT</b>: This implementation is likely to be useful only
046: * when processing HTTP requests.
047: *
048: * @author Craig R. McClanahan
049: * @author Remy Maucherat
050: * @version $Revision: 487694 $ $Date: 2006-12-15 23:30:51 +0100 (ven., 15 déc. 2006) $
051: */
052:
053: final class StandardHostValve extends ValveBase {
054:
055: private static Log log = LogFactory.getLog(StandardHostValve.class);
056:
057: // ----------------------------------------------------- Instance Variables
058:
059: /**
060: * The descriptive information related to this implementation.
061: */
062: private static final String info = "org.apache.catalina.core.StandardHostValve/1.0";
063:
064: /**
065: * The string manager for this package.
066: */
067: private static final StringManager sm = StringManager
068: .getManager(Constants.Package);
069:
070: // ------------------------------------------------------------- Properties
071:
072: /**
073: * Return descriptive information about this Valve implementation.
074: */
075: public String getInfo() {
076:
077: return (info);
078:
079: }
080:
081: // --------------------------------------------------------- Public Methods
082:
083: /**
084: * Select the appropriate child Context to process this request,
085: * based on the specified request URI. If no matching Context can
086: * be found, return an appropriate HTTP error.
087: *
088: * @param request Request to be processed
089: * @param response Response to be produced
090: * @param valveContext Valve context used to forward to the next Valve
091: *
092: * @exception IOException if an input/output error occurred
093: * @exception ServletException if a servlet error occurred
094: */
095: public final void invoke(Request request, Response response)
096: throws IOException, ServletException {
097:
098: // Select the Context to be used for this Request
099: Context context = request.getContext();
100: if (context == null) {
101: response.sendError(
102: HttpServletResponse.SC_INTERNAL_SERVER_ERROR, sm
103: .getString("standardHost.noContext"));
104: return;
105: }
106:
107: // Bind the context CL to the current thread
108: if (context.getLoader() != null) {
109: // Not started - it should check for availability first
110: // This should eventually move to Engine, it's generic.
111: Thread.currentThread().setContextClassLoader(
112: context.getLoader().getClassLoader());
113: }
114:
115: // Ask this Context to process this request
116: context.getPipeline().getFirst().invoke(request, response);
117:
118: // Access a session (if present) to update last accessed time, based on a
119: // strict interpretation of the specification
120: if (Globals.STRICT_SERVLET_COMPLIANCE) {
121: request.getSession(false);
122: }
123:
124: // Error page processing
125: response.setSuspended(false);
126:
127: Throwable t = (Throwable) request
128: .getAttribute(Globals.EXCEPTION_ATTR);
129:
130: if (t != null) {
131: throwable(request, response, t);
132: } else {
133: status(request, response);
134: }
135:
136: // Restore the context classloader
137: Thread.currentThread().setContextClassLoader(
138: StandardHostValve.class.getClassLoader());
139:
140: }
141:
142: /**
143: * Process Comet event.
144: *
145: * @param request Request to be processed
146: * @param response Response to be produced
147: * @param valveContext Valve context used to forward to the next Valve
148: *
149: * @exception IOException if an input/output error occurred
150: * @exception ServletException if a servlet error occurred
151: */
152: public final void event(Request request, Response response,
153: CometEvent event) throws IOException, ServletException {
154:
155: // Select the Context to be used for this Request
156: Context context = request.getContext();
157:
158: // Bind the context CL to the current thread
159: if (context.getLoader() != null) {
160: // Not started - it should check for availability first
161: // This should eventually move to Engine, it's generic.
162: Thread.currentThread().setContextClassLoader(
163: context.getLoader().getClassLoader());
164: }
165:
166: // Ask this Context to process this request
167: context.getPipeline().getFirst()
168: .event(request, response, event);
169:
170: // Access a session (if present) to update last accessed time, based on a
171: // strict interpretation of the specification
172: if (Globals.STRICT_SERVLET_COMPLIANCE) {
173: request.getSession(false);
174: }
175:
176: // Error page processing
177: response.setSuspended(false);
178:
179: Throwable t = (Throwable) request
180: .getAttribute(Globals.EXCEPTION_ATTR);
181:
182: if (t != null) {
183: throwable(request, response, t);
184: } else {
185: status(request, response);
186: }
187:
188: // Restore the context classloader
189: Thread.currentThread().setContextClassLoader(
190: StandardHostValve.class.getClassLoader());
191:
192: }
193:
194: // ------------------------------------------------------ Protected Methods
195:
196: /**
197: * Handle the specified Throwable encountered while processing
198: * the specified Request to produce the specified Response. Any
199: * exceptions that occur during generation of the exception report are
200: * logged and swallowed.
201: *
202: * @param request The request being processed
203: * @param response The response being generated
204: * @param throwable The exception that occurred (which possibly wraps
205: * a root cause exception
206: */
207: protected void throwable(Request request, Response response,
208: Throwable throwable) {
209: Context context = request.getContext();
210: if (context == null)
211: return;
212:
213: Throwable realError = throwable;
214:
215: if (realError instanceof ServletException) {
216: realError = ((ServletException) realError).getRootCause();
217: if (realError == null) {
218: realError = throwable;
219: }
220: }
221:
222: // If this is an aborted request from a client just log it and return
223: if (realError instanceof ClientAbortException) {
224: if (log.isDebugEnabled()) {
225: log.debug(sm.getString("standardHost.clientAbort",
226: realError.getCause().getMessage()));
227: }
228: return;
229: }
230:
231: ErrorPage errorPage = findErrorPage(context, throwable);
232: if ((errorPage == null) && (realError != throwable)) {
233: errorPage = findErrorPage(context, realError);
234: }
235:
236: if (errorPage != null) {
237: response.setAppCommitted(false);
238: request
239: .setAttribute(
240: ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
241: errorPage.getLocation());
242: request.setAttribute(
243: ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
244: new Integer(ApplicationFilterFactory.ERROR));
245: request.setAttribute(Globals.STATUS_CODE_ATTR, new Integer(
246: HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
247: request.setAttribute(Globals.ERROR_MESSAGE_ATTR, throwable
248: .getMessage());
249: request.setAttribute(Globals.EXCEPTION_ATTR, realError);
250: Wrapper wrapper = request.getWrapper();
251: if (wrapper != null)
252: request.setAttribute(Globals.SERVLET_NAME_ATTR, wrapper
253: .getName());
254: request.setAttribute(Globals.EXCEPTION_PAGE_ATTR, request
255: .getRequestURI());
256: request.setAttribute(Globals.EXCEPTION_TYPE_ATTR, realError
257: .getClass());
258: if (custom(request, response, errorPage)) {
259: try {
260: response.flushBuffer();
261: } catch (IOException e) {
262: container.getLogger().warn(
263: "Exception Processing " + errorPage, e);
264: }
265: }
266: } else {
267: // A custom error-page has not been defined for the exception
268: // that was thrown during request processing. Check if an
269: // error-page for error code 500 was specified and if so,
270: // send that page back as the response.
271: response
272: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
273: // The response is an error
274: response.setError();
275:
276: status(request, response);
277: }
278:
279: }
280:
281: /**
282: * Handle the HTTP status code (and corresponding message) generated
283: * while processing the specified Request to produce the specified
284: * Response. Any exceptions that occur during generation of the error
285: * report are logged and swallowed.
286: *
287: * @param request The request being processed
288: * @param response The response being generated
289: */
290: protected void status(Request request, Response response) {
291:
292: int statusCode = response.getStatus();
293:
294: // Handle a custom error page for this status code
295: Context context = request.getContext();
296: if (context == null)
297: return;
298:
299: /* Only look for error pages when isError() is set.
300: * isError() is set when response.sendError() is invoked. This
301: * allows custom error pages without relying on default from
302: * web.xml.
303: */
304: if (!response.isError())
305: return;
306:
307: ErrorPage errorPage = context.findErrorPage(statusCode);
308: if (errorPage != null) {
309: response.setAppCommitted(false);
310: request.setAttribute(Globals.STATUS_CODE_ATTR, new Integer(
311: statusCode));
312:
313: String message = RequestUtil.filter(response.getMessage());
314: if (message == null)
315: message = "";
316: request.setAttribute(Globals.ERROR_MESSAGE_ATTR, message);
317: request
318: .setAttribute(
319: ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
320: errorPage.getLocation());
321: request.setAttribute(
322: ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
323: new Integer(ApplicationFilterFactory.ERROR));
324:
325: Wrapper wrapper = request.getWrapper();
326: if (wrapper != null)
327: request.setAttribute(Globals.SERVLET_NAME_ATTR, wrapper
328: .getName());
329: request.setAttribute(Globals.EXCEPTION_PAGE_ATTR, request
330: .getRequestURI());
331: if (custom(request, response, errorPage)) {
332: try {
333: response.flushBuffer();
334: } catch (ClientAbortException e) {
335: // Ignore
336: } catch (IOException e) {
337: container.getLogger().warn(
338: "Exception Processing " + errorPage, e);
339: }
340: }
341: }
342:
343: }
344:
345: /**
346: * Find and return the ErrorPage instance for the specified exception's
347: * class, or an ErrorPage instance for the closest superclass for which
348: * there is such a definition. If no associated ErrorPage instance is
349: * found, return <code>null</code>.
350: *
351: * @param context The Context in which to search
352: * @param exception The exception for which to find an ErrorPage
353: */
354: protected static ErrorPage findErrorPage(Context context,
355: Throwable exception) {
356:
357: if (exception == null)
358: return (null);
359: Class clazz = exception.getClass();
360: String name = clazz.getName();
361: while (!Object.class.equals(clazz)) {
362: ErrorPage errorPage = context.findErrorPage(name);
363: if (errorPage != null)
364: return (errorPage);
365: clazz = clazz.getSuperclass();
366: if (clazz == null)
367: break;
368: name = clazz.getName();
369: }
370: return (null);
371:
372: }
373:
374: /**
375: * Handle an HTTP status code or Java exception by forwarding control
376: * to the location included in the specified errorPage object. It is
377: * assumed that the caller has already recorded any request attributes
378: * that are to be forwarded to this page. Return <code>true</code> if
379: * we successfully utilized the specified error page location, or
380: * <code>false</code> if the default error report should be rendered.
381: *
382: * @param request The request being processed
383: * @param response The response being generated
384: * @param errorPage The errorPage directive we are obeying
385: */
386: protected boolean custom(Request request, Response response,
387: ErrorPage errorPage) {
388:
389: if (container.getLogger().isDebugEnabled())
390: container.getLogger().debug("Processing " + errorPage);
391:
392: request.setPathInfo(errorPage.getLocation());
393:
394: try {
395:
396: // Reset the response if possible (else IllegalStateException)
397: //hres.reset();
398: // Reset the response (keeping the real error code and message)
399: Integer statusCodeObj = (Integer) request
400: .getAttribute(Globals.STATUS_CODE_ATTR);
401: int statusCode = statusCodeObj.intValue();
402: String message = (String) request
403: .getAttribute(Globals.ERROR_MESSAGE_ATTR);
404: response.reset(statusCode, message);
405:
406: // Forward control to the specified location
407: ServletContext servletContext = request.getContext()
408: .getServletContext();
409: RequestDispatcher rd = servletContext
410: .getRequestDispatcher(errorPage.getLocation());
411: rd.forward(request.getRequest(), response.getResponse());
412:
413: // If we forward, the response is suspended again
414: response.setSuspended(false);
415:
416: // Indicate that we have successfully processed this custom page
417: return (true);
418:
419: } catch (Throwable t) {
420:
421: // Report our failure to process this custom page
422: container.getLogger().error(
423: "Exception Processing " + errorPage, t);
424: return (false);
425:
426: }
427:
428: }
429:
430: }
|