blob: 43dbb47862423dd8c07df025721fa52b21d4ca06 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.engine.impl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestProgressTracker;
import org.apache.sling.api.request.ResponseUtil;
import org.apache.sling.api.servlets.ErrorHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.sling.api.SlingConstants.ERROR_REQUEST_URI;
import static org.apache.sling.api.SlingConstants.ERROR_SERVLET_NAME;
/**
* The <code>DefaultErrorHandler</code> is used by the
* {@link SlingRequestProcessorImpl} for error handling. It works
* in combination with the error filter chain. If a {@link ErrorHandler} service
* is registered, the actual response generated is delegated to that service.
*/
public class DefaultErrorHandler implements ErrorHandler {
/** default log */
private final Logger log = LoggerFactory.getLogger(getClass());
private volatile String serverInfo = ProductInfoProvider.PRODUCT_NAME;
/** Use this if not null, and if that fails output a report about that failure */
private volatile ErrorHandler delegate;
void setServerInfo(final String serverInfo) {
this.serverInfo = (serverInfo != null) ? serverInfo : ProductInfoProvider.PRODUCT_NAME;
}
public void setDelegate(final ErrorHandler eh) {
delegate = eh;
}
private void delegateFailed(
int originalStatus,
String originalMessage,
Throwable t,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// don't include Throwable in the response, gives too much information
final String m = "Error handler failed:" + t.getClass().getName();
log.error(m, t);
if (response.isCommitted()) {
log.warn("handleError: Response already committed; cannot send error " + originalStatus + " : "
+ originalMessage);
return;
}
// reset the response to clear headers and body
// the error filters are NOT called in this edge case
response.reset();
sendError(originalStatus, originalMessage, null, request, response);
}
// ---------- ErrorHandler interface (default implementation) --------------
/**
* Backend implementation of the HttpServletResponse.sendError methods.
* <p>
* This implementation resets the response before sending back a
* standardized response which just conveys the status, the message (either
* provided or a message derived from the status code), and server
* information.
* <p>
* This method logs error and does not write back and response data if the
* response has already been committed.
*/
@Override
public void handleError(
final int status,
String message,
final SlingHttpServletRequest request,
final SlingHttpServletResponse response)
throws IOException {
// If we have a delegate let it handle the error
if (delegate != null) {
try {
delegate.handleError(status, message, request, response);
} catch (final Exception e) {
delegateFailed(status, message, e, request, response);
}
return;
}
if (message == null) {
message = "HTTP ERROR:" + String.valueOf(status);
} else {
message = "HTTP ERROR:" + status + " - " + message;
}
sendError(status, message, null, request, response);
}
/**
* Backend implementation of handling uncaught throwables.
* <p>
* This implementation resets the response before sending back a
* standardized response which just conveys the status as 500/INTERNAL
* SERVER ERROR, the message from the throwable, the stacktrace, and server
* information.
* <p>
* This method logs error and does not write back and response data if the
* response has already been committed.
*/
@Override
public void handleError(
final Throwable throwable, final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws IOException {
final int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
// If we have a delegate let it handle the error
if (delegate != null) {
try {
delegate.handleError(throwable, request, response);
} catch (final Exception e) {
delegateFailed(status, throwable.toString(), e, request, response);
}
return;
}
sendError(status, throwable.getMessage(), throwable, request, response);
}
private void sendError(
final int status,
final String message,
final Throwable throwable,
final HttpServletRequest request,
final HttpServletResponse response)
throws IOException {
// error situation
final String servletName = (String) request.getAttribute(ERROR_SERVLET_NAME);
String requestURI = (String) request.getAttribute(ERROR_REQUEST_URI);
if (requestURI == null) {
requestURI = request.getRequestURI();
}
// set the status, content type and encoding
response.setStatus(status);
response.setContentType("text/html; charset=UTF-8");
final PrintWriter pw = response.getWriter();
pw.print("<html><head><title>");
if (message == null) {
pw.print("Internal error");
} else {
pw.print(ResponseUtil.escapeXml(message));
}
pw.println("</title></head><body><h1>");
if (throwable != null) {
pw.println(ResponseUtil.escapeXml(throwable.toString()));
} else if (message != null) {
pw.println(ResponseUtil.escapeXml(message));
} else {
pw.println("Internal error (no Exception to report)");
}
pw.println("</h1><p>");
pw.print("RequestURI=");
pw.println(ResponseUtil.escapeXml(request.getRequestURI()));
if (servletName != null) {
pw.println("</p><p>Servlet=");
pw.println(ResponseUtil.escapeXml(servletName));
}
pw.println("</p>");
if (throwable != null) {
final PrintWriter escapingWriter = new PrintWriter(ResponseUtil.getXmlEscapingWriter(pw));
pw.println("<h3>Exception stacktrace:</h3>");
pw.println("<pre>");
pw.flush();
throwable.printStackTrace(escapingWriter);
escapingWriter.flush();
pw.println("</pre>");
final RequestProgressTracker tracker = ((SlingHttpServletRequest) request).getRequestProgressTracker();
pw.println("<h3>Request Progress:</h3>");
pw.println("<pre>");
pw.flush();
tracker.dump(new PrintWriter(escapingWriter));
escapingWriter.flush();
pw.println("</pre>");
}
pw.println("<hr /><address>");
pw.println(ResponseUtil.escapeXml(serverInfo));
pw.println("</address></body></html>");
// commit the response
response.flushBuffer();
// close the response (SLING-2724)
pw.close();
}
}