KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > server > webapp > ErrorPageManager


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.server.webapp;
31
32 import com.caucho.Version;
33 import com.caucho.config.*;
34 import com.caucho.i18n.CharacterEncoding;
35 import com.caucho.java.LineMap;
36 import com.caucho.java.LineMapException;
37 import com.caucho.java.ScriptStackTrace;
38 import com.caucho.server.connection.AbstractHttpRequest;
39 import com.caucho.server.connection.AbstractHttpResponse;
40 import com.caucho.server.connection.CauchoRequest;
41 import com.caucho.server.connection.CauchoResponse;
42 import com.caucho.server.dispatch.BadRequestException;
43 import com.caucho.server.util.CauchoSystem;
44 import com.caucho.util.CharBuffer;
45 import com.caucho.util.CompileException;
46 import com.caucho.util.HTTPUtil;
47 import com.caucho.util.L10N;
48 import com.caucho.util.LineCompileException;
49 import com.caucho.util.Log;
50 import com.caucho.vfs.ClientDisconnectException;
51 import com.caucho.vfs.Encoding;
52
53 import javax.servlet.RequestDispatcher JavaDoc;
54 import javax.servlet.ServletException JavaDoc;
55 import javax.servlet.ServletRequest JavaDoc;
56 import javax.servlet.ServletResponse JavaDoc;
57 import javax.servlet.UnavailableException JavaDoc;
58 import javax.servlet.http.HttpServletRequest JavaDoc;
59 import javax.servlet.http.HttpServletResponse JavaDoc;
60 import java.io.CharArrayWriter JavaDoc;
61 import java.io.IOException JavaDoc;
62 import java.io.PrintWriter JavaDoc;
63 import java.util.HashMap JavaDoc;
64 import java.util.Locale JavaDoc;
65 import java.util.logging.Level JavaDoc;
66 import java.util.logging.Logger JavaDoc;
67
68 /**
69  * Represents the final servlet in a filter chain.
70  */

71 public class ErrorPageManager {
72   private final static L10N L = new L10N(ErrorPageManager.class);
73   private final static Logger JavaDoc log = Log.open(ErrorPageManager.class);
74   
75   public static final char []MSIE_PADDING;
76   
77   public static String JavaDoc REQUEST_URI = "javax.servlet.include.request_uri";
78   public static String JavaDoc CONTEXT_PATH = "javax.servlet.include.context_path";
79   public static String JavaDoc SERVLET_PATH = "javax.servlet.include.servlet_path";
80   public static String JavaDoc PATH_INFO = "javax.servlet.include.path_info";
81   public static String JavaDoc QUERY_STRING = "javax.servlet.include.query_string";
82   
83   public static String JavaDoc STATUS_CODE = "javax.servlet.error.status_code";
84   public static String JavaDoc EXCEPTION_TYPE = "javax.servlet.error.exception_type";
85   public static String JavaDoc MESSAGE = "javax.servlet.error.message";
86   public static String JavaDoc EXCEPTION = "javax.servlet.error.exception";
87   public static String JavaDoc ERROR_URI = "javax.servlet.error.request_uri";
88   public static String JavaDoc SERVLET_NAME = "javax.servlet.error.servlet_name";
89   
90   public static String JavaDoc JSP_EXCEPTION = "javax.servlet.jsp.jspException";
91   
92   public static String JavaDoc SHUTDOWN = "com.caucho.shutdown";
93   
94   private WebApp _app;
95   private WebAppContainer _appContainer;
96   private HashMap JavaDoc<Object JavaDoc,String JavaDoc> _errorPageMap = new HashMap JavaDoc<Object JavaDoc,String JavaDoc>();
97   private String JavaDoc _defaultLocation;
98
99   private ErrorPageManager _parent;
100
101   /**
102    * Create error page manager.
103    */

104   public ErrorPageManager()
105   {
106   }
107
108   /**
109    * Sets the manager parent.
110    */

111   public void setParent(ErrorPageManager parent)
112   {
113     _parent = parent;
114   }
115
116   /**
117    * Gets the manager parent.
118    */

119   public ErrorPageManager getParent()
120   {
121     return _parent;
122   }
123
124   /**
125    * Adds an error page.
126    */

127   public void addErrorPage(ErrorPage errorPage)
128   {
129     if (errorPage.getExceptionType() != null) {
130       _errorPageMap.put(errorPage.getExceptionType(),
131                         errorPage.getLocation());
132     }
133     else if (errorPage.getErrorCode() < 0) {
134       _defaultLocation = errorPage.getLocation();
135     }
136     else
137       _errorPageMap.put(new Integer JavaDoc(errorPage.getErrorCode()),
138                         errorPage.getLocation());
139   }
140
141   /**
142    * Sets the webApp.
143    */

144   public void setWebApp(WebApp app)
145   {
146     _app = app;
147   }
148
149   /**
150    * Sets the webApp container.
151    */

152   public void setWebAppContainer(WebAppContainer appContainer)
153   {
154     _appContainer = appContainer;
155   }
156   
157   /**
158    * Displays a parse error.
159    */

160   public void sendServletError(Throwable JavaDoc e,
161                                ServletRequest JavaDoc req,
162                                ServletResponse JavaDoc res)
163     throws IOException JavaDoc
164   {
165     HttpServletResponse JavaDoc response = (HttpServletResponse JavaDoc) res;
166     HttpServletRequest JavaDoc request = (HttpServletRequest JavaDoc) req;
167     Throwable JavaDoc rootExn = e;
168     Throwable JavaDoc errorPageExn = null;
169     LineMap lineMap = null;
170
171     if (response instanceof AbstractHttpResponse) {
172       ((AbstractHttpResponse) response).killCache();
173       ((AbstractHttpResponse) response).setNoCache(true);
174     }
175
176     try {
177       response.reset();
178     } catch (IllegalStateException JavaDoc e1) {
179     }
180
181     if (rootExn instanceof ClientDisconnectException)
182       throw (ClientDisconnectException) rootExn;
183
184     String JavaDoc location = null;
185
186     String JavaDoc title = "500 Servlet Exception";
187     boolean badRequest = false;
188     boolean doStackTrace = true;
189     boolean isCompileException = false;
190     boolean isLineCompileException = false;
191     boolean isServletException = false;
192     Throwable JavaDoc compileException = null;
193     String JavaDoc lineMessage = null;
194
195     boolean lookupErrorPage = true;
196     while (true) {
197       if (rootExn instanceof LineMapException)
198         lineMap = ((LineMapException) rootExn).getLineMap();
199
200       if (lookupErrorPage) {
201     errorPageExn = rootExn;
202       }
203
204       if (rootExn instanceof CompileException) {
205         doStackTrace = false;
206         isCompileException = true;
207
208         // use outer exception because it might have added more location info
209
if (rootExn instanceof LineCompileException) {
210       compileException = rootExn;
211       
212           isLineCompileException = true;
213         }
214     else if (compileException == null) // ! isLineCompileException)
215
compileException = rootExn;
216       }
217       else if (rootExn instanceof LineException) {
218     if (lineMessage == null)
219       lineMessage = rootExn.getMessage();
220       }
221
222       if (rootExn instanceof BadRequestException)
223         badRequest = true;
224
225       if (location != null || ! lookupErrorPage) {
226       }
227       else if (rootExn instanceof LineMapException &&
228            rootExn instanceof ServletException JavaDoc &&
229            ! (rootExn instanceof LineCompileException) &&
230            rootExn.getCause() != null) {
231     // hack to deal with JSP wrapping
232
}
233       else if (! isServletException) {
234     // SRV.9.9.2 Servlet 2.4
235
//location = getErrorPage(rootExn, ServletException.class);
236
location = getErrorPage(rootExn);
237     isServletException = true;
238       }
239       else {
240         location = getErrorPage(rootExn);
241     lookupErrorPage = false;
242       }
243
244       if (location != null)
245     lookupErrorPage = false;
246
247       Throwable JavaDoc cause = null;
248       if (rootExn instanceof ServletException JavaDoc &&
249       ! (rootExn instanceof LineCompileException))
250         cause = ((ServletException JavaDoc) rootExn).getRootCause();
251       else {
252     lookupErrorPage = false;
253         cause = rootExn.getCause();
254       }
255
256       if (cause != null)
257         rootExn = cause;
258       else {
259         break;
260       }
261     }
262
263     if (location == null && lookupErrorPage) {
264       location = getErrorPage(rootExn);
265     }
266
267     if (location == null)
268       location = getErrorPage(500);
269
270     if (location == null && _defaultLocation == null && _parent != null) {
271       _parent.sendServletError(e, req, res);
272       return;
273     }
274
275     if (isCompileException)
276       log.warning(compileException.getMessage());
277     else if (! doStackTrace)
278       log.warning(rootExn.toString());
279     else
280       log.log(Level.WARNING, e.toString(), e);
281
282     if (badRequest) {
283       title = rootExn.getMessage();
284       doStackTrace = false;
285       badRequest = true;
286
287       if (request instanceof CauchoRequest)
288     ((CauchoRequest) request).killKeepalive();
289       
290       response.resetBuffer();
291       
292       response.setStatus(response.SC_BAD_REQUEST, rootExn.getMessage());
293
294     }
295     else if (rootExn instanceof UnavailableException JavaDoc) {
296       UnavailableException JavaDoc unAvail = (UnavailableException JavaDoc) rootExn;
297
298       if (unAvail.isPermanent()) {
299     response.setStatus(HttpServletResponse.SC_NOT_FOUND);
300     title = "404 Not Found";
301
302     if (location == null)
303       location = getErrorPage(response.SC_NOT_FOUND);
304       }
305       else {
306     response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
307     title = "503 Unavailable";
308
309     if (unAvail.getUnavailableSeconds() > 0)
310       response.setIntHeader("Retry-After",
311                 unAvail.getUnavailableSeconds());
312     
313     if (location == null)
314       location = getErrorPage(response.SC_SERVICE_UNAVAILABLE);
315       }
316     }
317     /*
318     else if (_app != null && app.getServer().isClosed()) {
319       response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
320       title = "503 Unavailable";
321     }
322     */

323     else
324       response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
325     
326     if (location == null)
327       location = _defaultLocation;
328     
329     if (location != null) {
330       if (errorPageExn == null)
331     errorPageExn = rootExn;
332       
333       request.setAttribute(JSP_EXCEPTION, errorPageExn);
334       request.setAttribute(EXCEPTION, errorPageExn);
335       request.setAttribute(EXCEPTION_TYPE, errorPageExn.getClass());
336       if (request instanceof HttpServletRequest JavaDoc)
337         request.setAttribute(ERROR_URI,
338                              ((HttpServletRequest JavaDoc) request).getRequestURI());
339       if (request instanceof AbstractHttpRequest)
340         request.setAttribute(AbstractHttpRequest.SERVLET_NAME,
341                              ((AbstractHttpRequest) request).getServletName());
342
343       request.setAttribute(STATUS_CODE, new Integer JavaDoc(500));
344       request.setAttribute(MESSAGE, errorPageExn.getMessage());
345       /*
346         if (_app != null && _app.getServer().isClosed())
347         setAttribute(SHUTDOWN, "shutdown");
348       */

349
350       try {
351         RequestDispatcher JavaDoc disp = null;
352         // can't use filters because of error pages due to filters
353
// or security.
354

355     if (_app != null)
356       disp = _app.getRequestDispatcher(location);
357     else if (_appContainer != null)
358       disp = _appContainer.getRequestDispatcher(location);
359       
360         if (disp != null) {
361           ((RequestDispatcherImpl) disp).error(request, response);
362           return;
363         }
364       } catch (Throwable JavaDoc e1) {
365         log.log(Level.INFO, e1.toString(), e1);
366         rootExn = e1;
367       }
368     }
369
370     response.setContentType("text/html");
371
372     String JavaDoc encoding = CharacterEncoding.getLocalEncoding();
373
374     if (encoding != null)
375       response.setCharacterEncoding(encoding);
376     else {
377       Locale JavaDoc locale = Locale.getDefault();
378       if (! "ISO-8859-1".equals(Encoding.getMimeName(locale)))
379     response.setLocale(Locale.getDefault());
380     }
381
382     PrintWriter JavaDoc out = response.getWriter();
383
384     out.println("<html>");
385     if (! response.isCommitted())
386       out.println("<head><title>" + title + "</title></head>");
387     out.println("<body>");
388     out.println("<h1>" + title + "</h1>");
389
390     out.println("<code><pre>");
391     
392     /*
393     if (app != null && app.getServer().isClosed()) {
394       pw.println("Server is temporarily unavailable");
395       doStackTrace = false;
396     }
397     else
398     */

399
400     if (compileException != null)
401       out.println(escapeHtml(compileException.getMessage()));
402     else if (! doStackTrace)
403       out.println(escapeHtml(rootExn.toString()));
404
405     if (doStackTrace || log.isLoggable(Level.FINE)) {
406       printStackTrace(out, lineMessage, rootExn, lineMap);
407     }
408
409     out.println("</pre></code>");
410
411     if (CauchoSystem.isTesting() || _app == null) {
412     }
413     else {
414       out.println("<p /><hr />");
415       out.println("<small>");
416     
417       if (_app.getServer() != null
418       && _app.getServer().getServerHeader() != null) {
419     out.println(_app.getServer().getServerHeader());
420       }
421       else
422     out.println(com.caucho.Version.FULL_VERSION);
423       
424       out.println("</small>");
425     }
426       
427     out.println("</body></html>");
428
429     String JavaDoc userAgent = request.getHeader("User-Agent");
430       
431     if (userAgent != null && userAgent.indexOf("MSIE") >= 0) {
432       out.print(MSIE_PADDING);
433     }
434
435     out.close();
436   }
437
438   /**
439    * Sends an HTTP error to the browser.
440    *
441    * @param code the HTTP error code
442    * @param value a string message
443    */

444   public void sendError(CauchoRequest request,
445                         CauchoResponse response,
446                         int code, String JavaDoc message)
447     throws IOException JavaDoc
448   {
449     response.resetBuffer();
450     
451     /* XXX: if we've already got an error, won't this just mask it?
452     if (responseStream.isCommitted())
453       throw new IllegalStateException("response can't sendError() after commit");
454     */

455
456     response.setStatus(code, message);
457     
458     try {
459       if (handleErrorStatus(request, response, code, message) ||
460           code == HttpServletResponse.SC_NOT_MODIFIED)
461         return;
462
463       response.setContentType("text/html");
464       PrintWriter JavaDoc out = response.getWriter();
465
466       out.println("<html>");
467       if (! response.isCommitted()) {
468         out.print("<head><title>");
469         out.print(code);
470         out.print(" ");
471         out.print(message);
472         out.println("</title></head>");
473       }
474
475       out.println("<body>");
476       out.print("<h1>");
477       out.print(code);
478       out.print(" ");
479       out.print(message);
480       out.println("</h1>");
481
482       if (code == HttpServletResponse.SC_NOT_FOUND) {
483         out.println(L.l("{0} was not found on this server.",
484                         HTTPUtil.encodeString(request.getPageURI())));
485       }
486
487       if (! CauchoSystem.isTesting()) {
488     out.println("<p /><hr />");
489     out.println("<small>");
490     out.println(com.caucho.Version.FULL_VERSION);
491     out.println("</small>");
492       }
493       out.println("</body></html>");
494
495       String JavaDoc userAgent = request.getHeader("User-Agent");
496       
497       if (userAgent != null && userAgent.indexOf("MSIE") >= 0) {
498     out.write(MSIE_PADDING, 0, MSIE_PADDING.length);
499       }
500     } catch (Exception JavaDoc e) {
501       log.log(Level.WARNING, e.toString(), e);
502     }
503   }
504   
505   /**
506    * Handles an error status code.
507    *
508    * @return true if we've forwarded to an error page.
509    */

510   private boolean handleErrorStatus(CauchoRequest request,
511                                     CauchoResponse response,
512                                     int code, String JavaDoc message)
513     throws ServletException JavaDoc, IOException JavaDoc
514   {
515     if (code == HttpServletResponse.SC_OK ||
516         code == HttpServletResponse.SC_MOVED_TEMPORARILY ||
517         code == HttpServletResponse.SC_NOT_MODIFIED)
518       return false;
519
520     if (request.getRequestDepth(0) > 16)
521       return false;
522
523     else if (request.getAttribute(AbstractHttpRequest.ERROR_URI) != null)
524       return false;
525
526     response.killCache();
527     
528     String JavaDoc location = getErrorPage(code);
529       
530     if (location == null)
531       location = _defaultLocation;
532
533     if (location == null && _parent != null)
534       return _parent.handleErrorStatus(request, response, code, message);
535
536     if (_app == null && _appContainer == null)
537       return false;
538     
539     if (location != null && ! location.equals(request.getRequestURI())) {
540       // XXX: _responseStream.clearHeaders();
541

542       request.setAttribute(AbstractHttpRequest.STATUS_CODE,
543                             new Integer JavaDoc(code));
544       request.setAttribute(AbstractHttpRequest.MESSAGE,
545                            message);
546       request.setAttribute(AbstractHttpRequest.ERROR_URI,
547                            request.getRequestURI());
548       if (request instanceof AbstractHttpRequest)
549         request.setAttribute(AbstractHttpRequest.SERVLET_NAME,
550                              ((AbstractHttpRequest) request).getServletName());
551
552       try {
553         RequestDispatcher JavaDoc disp = null;
554         // can't use filters because of error pages due to filters
555
// or security.
556
if (_app != null)
557       disp = _app.getRequestDispatcher(location);
558     else if (_appContainer != null)
559       disp = _appContainer.getRequestDispatcher(location);
560
561         //disp.forward(request, this, "GET", false);
562

563     if (disp != null)
564       ((RequestDispatcherImpl) disp).error(request, response);
565     else
566       return false;
567       } catch (Throwable JavaDoc e) {
568         sendServletError(e, request, response);
569       }
570           
571       return true;
572     }
573
574     return false;
575   }
576
577   /**
578    * Returns the URL of an error page for the given exception.
579    */

580   String JavaDoc getErrorPage(Throwable JavaDoc e)
581   {
582     return getErrorPage(e, Throwable JavaDoc.class);
583   }
584   
585   /**
586    * Returns the URL of an error page for the given exception.
587    */

588   String JavaDoc getErrorPage(Throwable JavaDoc e, Class JavaDoc limit)
589   {
590     Class JavaDoc cl = e.getClass();
591     for (; cl != null; cl = cl.getSuperclass()) {
592       String JavaDoc location = (String JavaDoc) _errorPageMap.get(cl.getName());
593       if (location != null)
594         return location;
595
596       if (cl == limit)
597         break;
598     }
599     
600     for (cl = e.getClass(); cl != null; cl = cl.getSuperclass()) {
601       String JavaDoc name = cl.getName();
602       int p = name.lastIndexOf('.');
603
604       if (p > 0) {
605         name = name.substring(p + 1);
606
607         String JavaDoc location = (String JavaDoc) _errorPageMap.get(name);
608         if (location != null)
609           return location;
610       }
611       
612       if (cl == limit)
613         break;
614     }
615
616     return null;
617   }
618   
619   /**
620    * Returns the URL of an error page for the given exception.
621    */

622   String JavaDoc getErrorPage(int code)
623   {
624     Integer JavaDoc key = new Integer JavaDoc(code);
625
626     String JavaDoc location = (String JavaDoc) _errorPageMap.get(key);
627     if (location != null)
628       return location;
629
630     return (String JavaDoc) _errorPageMap.get(new Integer JavaDoc(0));
631   }
632
633   /**
634    * Escapes HTML symbols in a stack trace.
635    */

636   private void printStackTrace(PrintWriter JavaDoc out,
637                    String JavaDoc lineMessage,
638                    Throwable JavaDoc e,
639                                LineMap lineMap)
640   {
641     CharArrayWriter JavaDoc writer = new CharArrayWriter JavaDoc();
642     PrintWriter JavaDoc pw = new PrintWriter JavaDoc(writer);
643
644     if (lineMessage != null)
645       pw.println(lineMessage);
646
647     if (lineMap != null)
648       lineMap.printStackTrace(e, pw);
649     else
650       ScriptStackTrace.printStackTrace(e, pw);
651
652     pw.close();
653
654     char []array = writer.toCharArray();
655     out.print(escapeHtml(new String JavaDoc(array)));
656   }
657
658   /**
659    * Escapes special symbols in a string. For example '<' becomes '&lt;'
660    */

661   private String JavaDoc escapeHtml(String JavaDoc s)
662   {
663     if (s == null)
664       return null;
665
666     CharBuffer cb = new CharBuffer();
667     int lineCharacter = 0;
668     boolean startsWithSpace = false;
669     
670     for (int i = 0; i < s.length(); i++) {
671       char ch = s.charAt(i);
672
673       lineCharacter++;
674       
675       if (ch == '<')
676     cb.append("&lt;");
677       else if (ch == '&')
678     cb.append("&amp;");
679       else if (ch == '\n' || ch == '\r') {
680         lineCharacter = 0;
681     cb.append(ch);
682         startsWithSpace = false;
683       }
684       else if (lineCharacter > 70 && ch == ' ' && ! startsWithSpace) {
685         lineCharacter = 0;
686         cb.append('\n');
687         for (; i + 1 < s.length() && s.charAt(i + 1) == ' '; i++) {
688         }
689       }
690       else if (lineCharacter == 1 && (ch == ' ' || ch == '\t')) {
691         cb.append((char) ch);
692         startsWithSpace = true;
693       }
694       else
695     cb.append(ch);
696     }
697
698     return cb.toString();
699   }
700
701   static {
702     MSIE_PADDING = ("\n\n\n\n" +
703                     "<!--\n" +
704                     " - Unfortunately, Microsoft has added a clever new\n" +
705                     " - \"feature\" to Internet Explorer. If the text in\n" +
706                     " - an error's message is \"too small\", specifically\n" +
707                     " - less than 512 bytes, Internet Explorer returns\n" +
708                     " - its own error message. Yes, you can turn that\n" +
709                     " - off, but *surprise* it's pretty tricky to find\n" +
710                     " - buried as a switch called \"smart error\n" +
711                     " - messages\" That means, of course, that many of\n" +
712                     " - Resin's error messages are censored by default.\n" +
713                     " - And, of course, you'll be shocked to learn that\n" +
714                     " - IIS always returns error messages that are long\n" +
715                     " - enough to make Internet Explorer happy. The\n" +
716                     " - workaround is pretty simple: pad the error\n" +
717                     " - message with a big comment to push it over the\n" +
718                     " - five hundred and twelve byte minimum. Of course,\n" +
719                     " - that's exactly what you're reading right now.\n" +
720                     " -->\n").toCharArray();
721   }
722 }
723
Popular Tags