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