001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: ExceptionHandler.java,v 1.2 2006-06-15 13:44:07 sinisa Exp $
022: */
023:
024: package com.lutris.appserver.server.httpPresentation;
025:
026: import java.io.ByteArrayOutputStream;
027: import java.io.File;
028: import java.io.IOException;
029: import java.io.PrintWriter;
030:
031: import com.lutris.appserver.server.Application;
032: import com.lutris.logging.LogChannel;
033: import com.lutris.logging.Logger;
034:
035: /**
036: * Various routines used in handling exceptions.
037: */
038: class ExceptionHandler {
039: /*
040: * Disallow instances.
041: */
042: private ExceptionHandler() {
043: }
044:
045: /**
046: * Generate an HTML page describing an exception and log the exception.
047: * If it looks like a client disconnect, log at the DEBUG level.
048: */
049: private static void sendExceptionPage(HttpPresentationComms comms,
050: Application application, Throwable except)
051: throws HttpPresentationException, IOException {
052:
053: String title = "Error in " + application.getName();
054:
055: comms.response
056: .setStatus(HttpPresentationResponse.SC_INTERNAL_SERVER_ERROR);
057: comms.response.setContentType("text/html");
058:
059: HttpPresentationOutputStream out = comms.response
060: .getOutputStream();
061:
062: out.println("<HTML><TITLE>");
063: out.println(title);
064: out.println("</TITLE>");
065:
066: out.println("<BODY BGCOLOR=white>");
067: out.println("<H2><P><CENTER><FONT COLOR=red> - " + title
068: + " - </FONT></CENTER></H2>");
069:
070: // Reason...
071: if (except instanceof java.io.FileNotFoundException) {
072: out.println("<B>File not found</B>: "
073: + comms.request.getRequestURI());
074: } else if (except instanceof DirectoryException) {
075: out.println("<B>Directory access error</B>: "
076: + comms.request.getRequestURI());
077: } else if (except instanceof java.lang.IllegalAccessException) {
078: out.println("<B>Illegal access</B>: "
079: + comms.request.getRequestURI());
080: } else if ((except instanceof java.lang.ClassNotFoundException)
081: || (except instanceof java.lang.NoClassDefFoundError)) {
082: out.println("<B>Class not found: " + except.getMessage()
083: + "</B>");
084: } else {
085: // catch all
086: out.println("<B>Reason: </B>" + except.getClass() + ": "
087: + except.getMessage());
088: }
089:
090: // Details...
091: out.println("<P>");
092: out.println("<B>Java Call Stack:</B><P><FONT size=-1><PRE>");
093: ByteArrayOutputStream b = new ByteArrayOutputStream(); //FIX: This should be a utility.
094: PrintWriter p = new PrintWriter(b);
095: except.printStackTrace(p);
096: p.flush();
097: out.println(b.toString());
098: out.println("</PRE></FONT>\n");
099:
100: // Back button
101: out.println("<FORM>");
102: out.println("<CENTER><INPUT TYPE=BUTTON VALUE=\"back\"");
103: out.println("onClick=\"history.go(-1)\"></CENTER>\n");
104: out.println("</FORM>");
105:
106: // EOF
107: out.println("</BODY>");
108: out.println("</HTML>");
109: comms.response.flush();
110: }
111:
112: /**
113: * Output an HTML page describing an exception and log the exception.
114: * If it looks like a client disconnect, log at the DEBUG level.
115: */
116: protected static void logDisplayException(
117: HttpPresentationComms comms, LogChannel logChannel,
118: long requestId, Application application, Throwable except,
119: String presObjPath) {
120:
121: /*
122: * Log at DEBUG if client I/O error; but generate page anyway. It
123: * will most likely be discarded, but it will prevent just a blank
124: * page if the client is still connected.
125: */
126: try {
127: if (HttpPresentationIOException.isClientIOException(except)) {
128: logChannel.write(Logger.DEBUG, "RID:" + requestId
129: + ": Client appeared to drop connection: "
130: + presObjPath);
131: sendExceptionPage(comms, application, except);
132: } else {
133: logChannel.write(Logger.ERROR, "RID:" + requestId
134: + ": Unhandled Application error in "
135: + application.getName(), except);
136: sendExceptionPage(comms, application, except);
137: }
138: } catch (IllegalStateException except2) {
139: // May not be able set set headers if output has begun.
140: } catch (IOException except2) {
141: // Ignore, client is gone
142: } catch (HttpPresentationException except2) {
143: // Ignore, client maybe gone
144: }
145: }
146:
147: /**
148: * Get the path of the error handler for an exception that occured during
149: * the loading and execution of a presentation or the handling of an error
150: * The handler is a presentation named `ErrorHandler' in the current or
151: * a higher level directory.
152: *
153: * This method just returns the path for the error handler for the
154: * specified presentation path. This attempts to load the error handler
155: * just to see if it exists. This also handles errors that are throw while
156: * handling an error (which is often just an ErrorHandler passing an error up
157: * to a higher level).
158: *
159: * @param presObjPath
160: * The path of the presentation object that threw the error. This
161: * may not be the same as the original request URL. This is relative
162: * to <CODE>presentationRoot</CODE>.
163: * @param requestedPresObjPath
164: * Original presentation object that the error hander is being called
165: * for.
166: * @param whileHandlingError
167: * True an error was already being handled.
168: * @param logChannel
169: * Log channel to use.
170: * @return The path to the presentation object to invoke or null if no
171: * is available (reached top of tree).
172: */
173: protected static String getErrorHandler(String presObjPath,
174: String requestedPresObjPath, boolean whileHandlingError,
175: PresentationLoader loader, LogChannel logChannel,
176: long requestId) {
177: String handlerDir;
178:
179: logChannel.write(Logger.DEBUG, "RID:" + requestId
180: + ": Searching for error handler for "
181: + requestedPresObjPath);
182:
183: // Find first place to look.
184: if (!whileHandlingError) {
185: // Look in current package; its ok for dir to be null here.
186: handlerDir = (new File(presObjPath)).getParent();
187: } else {
188: // Look in parent package.
189: String currentDir = (new File(presObjPath)).getParent();
190: if (currentDir == null) {
191: handlerDir = null;
192: } else {
193: handlerDir = (new File(currentDir)).getParent();
194: // already at top level, don't try that handler again
195: if (handlerDir == null) {
196: logChannel.write(Logger.DEBUG, "RID:" + requestId
197: + ": No error handler found for: "
198: + presObjPath);
199: return null;
200: }
201: }
202: }
203:
204: // Try to load, searching up if a handler was not found.
205: while (true) {
206: // handlerDir can be null here (trying the top).
207: String handlerPath = (new File(handlerDir,
208: "ErrorHandler.po")).getPath();
209: logChannel.write(Logger.DEBUG, "RID:" + requestId
210: + ": Checking for error handler: " + handlerPath);
211: try {
212: loader.loadPresentation(handlerPath);
213: return handlerPath; // Found and loaded a handler.
214: } catch (ClassNotFoundException noClass) {
215: if (handlerDir == null) {
216: break; // Not found
217: }
218: String currentDir = (new File(handlerPath)).getParent();
219: if (currentDir == null) {
220: handlerDir = null;
221: } else {
222: handlerDir = (new File(currentDir)).getParent();
223: }
224: } catch (Throwable except) {
225: logChannel.write(Logger.DEBUG, "RID:" + requestId
226: + ": Error while loading error handler: "
227: + handlerPath, except);
228: }
229: }
230:
231: logChannel.write(Logger.DEBUG, "RID:" + requestId
232: + ": No error handler found for: " + presObjPath);
233: return null;
234: }
235:
236: /**
237: * Send a simple HTML page as part of a response. This method
238: * composes a complete page with header and body sections.
239: */
240: private static void sendHtml(HttpPresentationComms comms,
241: String title, String htmlText)
242: throws HttpPresentationException, IOException {
243:
244: comms.response.setContentType("text/html");
245: HttpPresentationOutputStream out = comms.response
246: .getOutputStream();
247:
248: out.println("<HTML>");
249: out.println("<TITLE>" + title + "</TITLE>");
250: out.println("<BODY>");
251: out.println(htmlText);
252: out.println("</BODY>");
253: out.println("</HTML>");
254: comms.response.flush();
255: }
256:
257: /**
258: * Process an unauthorized page exception, sending the approriate
259: * HTTP response. <P>
260: *
261: * Two actions are taken: a HTTP header is set telling the browser that it
262: * needs to use basic auth authentication and which relm to ask for, and
263: * the response code is set to 401, which means unauthorized. The response
264: * is now ready to return to the user, no further proccessing needs to be
265: * done on it. Most browsers will see the 401 response, and will create a
266: * dialog box asking the user to enter a username and password. The relm
267: * appears in this dialog, so the user knows what he or she is logging in
268: * to. After gathering the data, most browsers will re-issue the response,
269: * with the username and password in an HTTP header.
270: */
271: protected static void sendUnauthorizedPage(
272: HttpPresentationComms comms, LogChannel logChannel,
273: long requestId, Application application,
274: String presObjPath, PageUnauthorizedException unauthExcept)
275: throws HttpPresentationException, IOException {
276:
277: try {
278: logChannel.write(Logger.DEBUG, "RID:" + requestId
279: + ": Authentication required: " + presObjPath);
280:
281: comms.response.setHeader("WWW-authenticate",
282: "basic realm=\"" + unauthExcept.getRelm() + "\"");
283: comms.response
284: .setStatus(HttpPresentationResponse.SC_UNAUTHORIZED);
285: sendHtml(comms, unauthExcept.getHtmlTitle(), unauthExcept
286: .getHtmlText());
287: comms.response.flush();
288: } catch (Exception except) {
289: // Make chained exception
290: HttpPresentationException presExcept = new HttpPresentationException(
291: "Exception while sending page unauthorized response",
292: except);
293: logDisplayException(comms, logChannel, requestId,
294: application, presExcept, presObjPath);
295: }
296: }
297:
298: /**
299: * Generate an error for the maximum number of redirect's being
300: * reached.
301: */
302: protected static void maxRedirectsReached(
303: HttpPresentationComms comms, LogChannel logChannel,
304: long requestId, int maxRedirectErrorLoops,
305: Application application, String presObjPath)
306: throws HttpPresentationException, IOException {
307:
308: String msg = "Maximum number of server redirects or ErrorHandler "
309: + "calls per request reached ("
310: + maxRedirectErrorLoops
311: + "); probable endless loop: " + presObjPath;
312: HttpPresentationException except = new HttpPresentationException(
313: msg, comms.exception);
314: logDisplayException(comms, logChannel, requestId, application,
315: except, presObjPath);
316: }
317:
318: /**
319: * Process an exception that has not been handled by an error handler or
320: * so other mechanism.
321: */
322: protected static void processUnhandledException(
323: HttpPresentationComms comms, LogChannel logChannel,
324: long requestId, Application application,
325: String presObjPath, Throwable except) {
326:
327: try {
328: if ((except instanceof ClassNotFoundException)
329: || (except instanceof NoClassDefFoundError)
330: || (except instanceof FilePresentationException)) {
331:
332: logChannel.write(Logger.WARNING, "RID:" + requestId
333: + ": page not found: " + presObjPath);
334: comms.response
335: .setStatus(HttpPresentationResponse.SC_NOT_FOUND);
336: sendHtml(comms, "Page Not Found",
337: "<B>Page was not found on this server</B>");
338: } else {
339: logDisplayException(comms, logChannel, requestId,
340: application, except, presObjPath);
341: }
342: } catch (Throwable except2) {
343: // Error handling the error; hang it up.
344: }
345: }
346: }
|