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
19 package org.apache.catalina.valves;
20
21
22 import java.io.IOException;
23 import java.io.Writer;
24
25 import javax.servlet.ServletException;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.apache.catalina.Globals;
29 import org.apache.catalina.connector.Request;
30 import org.apache.catalina.connector.Response;
31 import org.apache.catalina.util.RequestUtil;
32 import org.apache.catalina.util.ServerInfo;
33 import org.apache.catalina.util.StringManager;
34
35 /**
36 * <p>Implementation of a Valve that outputs HTML error pages.</p>
37 *
38 * <p>This Valve should be attached at the Host level, although it will work
39 * if attached to a Context.</p>
40 *
41 * <p>HTML code from the Cocoon 2 project.</p>
42 *
43 * @author Remy Maucherat
44 * @author Craig R. McClanahan
45 * @author <a href="mailto:nicolaken@supereva.it">Nicola Ken Barozzi</a> Aisa
46 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
47 * @author Yoav Shapira
48 * @version $Revision: 543307 $ $Date: 2007-06-01 01:08:24 +0200 (ven., 01 juin 2007) $
49 */
50
51 public class ErrorReportValve
52 extends ValveBase {
53
54
55 // ----------------------------------------------------- Instance Variables
56
57
58 /**
59 * The descriptive information related to this implementation.
60 */
61 private static final String info =
62 "org.apache.catalina.valves.ErrorReportValve/1.0";
63
64
65 /**
66 * The StringManager for this package.
67 */
68 protected static StringManager sm =
69 StringManager.getManager(Constants.Package);
70
71
72 // ------------------------------------------------------------- Properties
73
74
75 /**
76 * Return descriptive information about this Valve implementation.
77 */
78 public String getInfo() {
79
80 return (info);
81
82 }
83
84
85 // --------------------------------------------------------- Public Methods
86
87
88 /**
89 * Invoke the next Valve in the sequence. When the invoke returns, check
90 * the response state, and output an error report is necessary.
91 *
92 * @param request The servlet request to be processed
93 * @param response The servlet response to be created
94 *
95 * @exception IOException if an input/output error occurs
96 * @exception ServletException if a servlet error occurs
97 */
98 public void invoke(Request request, Response response)
99 throws IOException, ServletException {
100
101 // Perform the request
102 getNext().invoke(request, response);
103
104 Throwable throwable =
105 (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
106
107 if (response.isCommitted()) {
108 return;
109 }
110
111 if (throwable != null) {
112
113 // The response is an error
114 response.setError();
115
116 // Reset the response (if possible)
117 try {
118 response.reset();
119 } catch (IllegalStateException e) {
120 ;
121 }
122
123 response.sendError
124 (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
125
126 }
127
128 response.setSuspended(false);
129
130 try {
131 report(request, response, throwable);
132 } catch (Throwable tt) {
133 ;
134 }
135
136 }
137
138
139 // ------------------------------------------------------ Protected Methods
140
141
142 /**
143 * Prints out an error report.
144 *
145 * @param request The request being processed
146 * @param response The response being generated
147 * @param throwable The exception that occurred (which possibly wraps
148 * a root cause exception
149 */
150 protected void report(Request request, Response response,
151 Throwable throwable) {
152
153 // Do nothing on non-HTTP responses
154 int statusCode = response.getStatus();
155
156 // Do nothing on a 1xx, 2xx and 3xx status
157 // Do nothing if anything has been written already
158 if ((statusCode < 400) || (response.getContentCount() > 0))
159 return;
160
161 String message = RequestUtil.filter(response.getMessage());
162 if (message == null)
163 message = "";
164
165 // Do nothing if there is no report for the specified status code
166 String report = null;
167 try {
168 report = sm.getString("http." + statusCode, message);
169 } catch (Throwable t) {
170 ;
171 }
172 if (report == null)
173 return;
174
175 StringBuffer sb = new StringBuffer();
176
177 sb.append("<html><head><title>");
178 sb.append(ServerInfo.getServerInfo()).append(" - ");
179 sb.append(sm.getString("errorReportValve.errorReport"));
180 sb.append("</title>");
181 sb.append("<style><!--");
182 sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
183 sb.append("--></style> ");
184 sb.append("</head><body>");
185 sb.append("<h1>");
186 sb.append(sm.getString("errorReportValve.statusHeader",
187 "" + statusCode, message)).append("</h1>");
188 sb.append("<HR size=\"1\" noshade=\"noshade\">");
189 sb.append("<p><b>type</b> ");
190 if (throwable != null) {
191 sb.append(sm.getString("errorReportValve.exceptionReport"));
192 } else {
193 sb.append(sm.getString("errorReportValve.statusReport"));
194 }
195 sb.append("</p>");
196 sb.append("<p><b>");
197 sb.append(sm.getString("errorReportValve.message"));
198 sb.append("</b> <u>");
199 sb.append(message).append("</u></p>");
200 sb.append("<p><b>");
201 sb.append(sm.getString("errorReportValve.description"));
202 sb.append("</b> <u>");
203 sb.append(report);
204 sb.append("</u></p>");
205
206 if (throwable != null) {
207
208 String stackTrace = getPartialServletStackTrace(throwable);
209 sb.append("<p><b>");
210 sb.append(sm.getString("errorReportValve.exception"));
211 sb.append("</b> <pre>");
212 sb.append(RequestUtil.filter(stackTrace));
213 sb.append("</pre></p>");
214
215 int loops = 0;
216 Throwable rootCause = throwable.getCause();
217 while (rootCause != null && (loops < 10)) {
218 stackTrace = getPartialServletStackTrace(rootCause);
219 sb.append("<p><b>");
220 sb.append(sm.getString("errorReportValve.rootCause"));
221 sb.append("</b> <pre>");
222 sb.append(RequestUtil.filter(stackTrace));
223 sb.append("</pre></p>");
224 // In case root cause is somehow heavily nested
225 rootCause = rootCause.getCause();
226 loops++;
227 }
228
229 sb.append("<p><b>");
230 sb.append(sm.getString("errorReportValve.note"));
231 sb.append("</b> <u>");
232 sb.append(sm.getString("errorReportValve.rootCauseInLogs",
233 ServerInfo.getServerInfo()));
234 sb.append("</u></p>");
235
236 }
237
238 sb.append("<HR size=\"1\" noshade=\"noshade\">");
239 sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
240 sb.append("</body></html>");
241
242 try {
243 try {
244 response.setContentType("text/html");
245 response.setCharacterEncoding("utf-8");
246 } catch (Throwable t) {
247 if (container.getLogger().isDebugEnabled())
248 container.getLogger().debug("status.setContentType", t);
249 }
250 Writer writer = response.getReporter();
251 if (writer != null) {
252 // If writer is null, it's an indication that the response has
253 // been hard committed already, which should never happen
254 writer.write(sb.toString());
255 }
256 } catch (IOException e) {
257 ;
258 } catch (IllegalStateException e) {
259 ;
260 }
261
262 }
263
264
265 /**
266 * Print out a partial servlet stack trace (truncating at the last
267 * occurrence of javax.servlet.).
268 */
269 protected String getPartialServletStackTrace(Throwable t) {
270 StringBuffer trace = new StringBuffer();
271 trace.append(t.toString()).append('\n');
272 StackTraceElement[] elements = t.getStackTrace();
273 int pos = elements.length;
274 for (int i = 0; i < elements.length; i++) {
275 if ((elements[i].getClassName().startsWith
276 ("org.apache.catalina.core.ApplicationFilterChain"))
277 && (elements[i].getMethodName().equals("internalDoFilter"))) {
278 pos = i;
279 }
280 }
281 for (int i = 0; i < pos; i++) {
282 if (!(elements[i].getClassName().startsWith
283 ("org.apache.catalina.core."))) {
284 trace.append('\t').append(elements[i].toString()).append('\n');
285 }
286 }
287 return trace.toString();
288 }
289
290 }