org.omnifaces.exceptionhandler.FullAjaxExceptionHandler Maven / Gradle / Ivy
/*
* Copyright 2012 OmniFaces.
*
* Licensed 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.omnifaces.exceptionhandler;
import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE;
import static javax.servlet.RequestDispatcher.ERROR_MESSAGE;
import static javax.servlet.RequestDispatcher.ERROR_REQUEST_URI;
import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;
import static org.omnifaces.util.Exceptions.unwrap;
import static org.omnifaces.util.Faces.getContext;
import static org.omnifaces.util.FacesLocal.getRequest;
import static org.omnifaces.util.FacesLocal.normalizeViewId;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELException;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PreRenderViewEvent;
import javax.faces.view.ViewDeclarationLanguage;
import javax.faces.webapp.FacesServlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.omnifaces.config.WebXml;
import org.omnifaces.context.OmniPartialViewContext;
import org.omnifaces.filter.FacesExceptionFilter;
import org.omnifaces.util.Exceptions;
import org.omnifaces.util.Hacks;
/**
*
* The {@link FullAjaxExceptionHandler} will transparently handle exceptions during ajax requests exactly the same way
* as exceptions during synchronous (non-ajax) requests.
*
* By default, when an exception occurs during a JSF ajax request, the enduser would not get any form of feedback if the
* action was successfully performed or not. In Mojarra, only when the project stage is set to Development
,
* the enduser would see a bare JavaScript alert with only the exception type and message. It would make sense if
* exceptions during ajax requests are handled the same way as exceptions during synchronous requests, which is
* utilizing the standard Servlet API <error-page>
mechanisms in web.xml
.
*
*
Installation
*
* This handler must be registered by a factory as follows in faces-config.xml
in order to get it to run:
*
* <factory>
* <exception-handler-factory>org.omnifaces.exceptionhandler.FullAjaxExceptionHandlerFactory</exception-handler-factory>
* </factory>
*
*
* Error pages
*
* This exception handler will parse the web.xml
and web-fragment.xml
files to find the error
* page locations of the HTTP error code 500
and all declared specific exception types. Those locations
* need to point to Facelets files (JSP is not supported) and the URL must match the {@link FacesServlet} mapping (just
* mapping it on *.xhtml
should eliminate confusion about virtual URLs).
*
* The location of the HTTP error code 500
or the exception type java.lang.Throwable
is
* required in order to get the {@link FullAjaxExceptionHandler} to work, because there's then at least a fall
* back error page whenever there's no match with any of the declared specific exceptions. So, you must at least have
* either
*
* <error-page>
* <error-code>500</error-code>
* <location>/WEB-INF/errorpages/500.xhtml</location>
* </error-page>
*
* or
*
* <error-page>
* <exception-type>java.lang.Throwable</exception-type>
* <location>/WEB-INF/errorpages/500.xhtml</location>
* </error-page>
*
*
* You can have both, but the java.lang.Throwable
one will always get precedence over the 500
* one, as per the Servlet API specification, so the 500
one would be basically superfluous.
*
* The exception detail is available in the request scope by the standard Servlet error request attributes like as in a
* normal synchronous error page response. You could for example show them in the error page as follows:
*
* <ul>
* <li>Date/time: #{of:formatDate(now, 'yyyy-MM-dd HH:mm:ss')}</li>
* <li>User agent: #{header['user-agent']}</li>
* <li>User IP: #{request.remoteAddr}</li>
* <li>Request URI: #{requestScope['javax.servlet.error.request_uri']}</li>
* <li>Ajax request: #{facesContext.partialViewContext.ajaxRequest ? 'Yes' : 'No'}</li>
* <li>Status code: #{requestScope['javax.servlet.error.status_code']}</li>
* <li>Exception type: #{requestScope['javax.servlet.error.exception_type']}</li>
* <li>Exception message: #{requestScope['javax.servlet.error.message']}</li>
* <li>Stack trace:
* <pre>#{of:printStackTrace(requestScope['javax.servlet.error.exception'])}</pre>
* </li>
* </ul>
*
*
* Exceptions during render response can only be handled when the javax.faces.FACELETS_BUFFER_SIZE
is
* large enough so that the so far rendered response until the occurrence of the exception fits in there and can
* therefore safely be resetted. When rendering of the error page itself fails due to a bug in the error page itself
* and the response can still be resetted, then a hardcoded message will be returned informing the developer about the
* double mistake.
*
*
Error in error page itself
*
* When the rendering of the error page itself failed due to a bug in the error page itself, then the
* {@link FullAjaxExceptionHandler} will reset the response and display a hardcoded error message in "plain text".
*
*
Normal requests
*
* Note that the {@link FullAjaxExceptionHandler} does not deal with normal (non-ajax) requests at all. To properly
* handle JSF and EL exceptions on normal requests as well, you need an additional {@link FacesExceptionFilter}. This
* will extract the root cause from a wrapped {@link FacesException} and {@link ELException} before delegating the
* {@link ServletException} further to the container (the container will namely use the first root cause of
* {@link ServletException} to match an error page by exception in web.xml).
*
*
Customizing FullAjaxExceptionHandler
*
* If more fine grained control is desired for determining the root cause of the caught exception, or whether it should
* be handled, or determining the error page, or logging the exception, then the developer can opt to extend this
* {@link FullAjaxExceptionHandler} and override one or more of the following protected methods:
*
* - {@link #findExceptionRootCause(FacesContext, Throwable)}
*
- {@link #shouldHandleExceptionRootCause(FacesContext, Throwable)}
*
- {@link #findErrorPageLocation(FacesContext, Throwable)}
*
- {@link #logException(FacesContext, Throwable, String, String, Object...)}
*
*
* Don't forget to create a custom {@link ExceptionHandlerFactory} for it as well, so that it could be registered
* in faces-config.xml
. This does not necessarily need to extend from
* {@link FullAjaxExceptionHandlerFactory}.
*
* @author Bauke Scholtz
* @see FullAjaxExceptionHandlerFactory
*/
public class FullAjaxExceptionHandler extends ExceptionHandlerWrapper {
// Private constants ----------------------------------------------------------------------------------------------
private static final Logger logger = Logger.getLogger(FullAjaxExceptionHandler.class.getName());
private static final String ERROR_DEFAULT_LOCATION_MISSING =
"Either HTTP 500 or java.lang.Throwable error page is required in web.xml or web-fragment.xml."
+ " Neither was found.";
private static final String LOG_EXCEPTION_HANDLED =
"FullAjaxExceptionHandler: An exception occurred during processing JSF ajax request."
+ " Error page '%s' will be shown.";
private static final String LOG_RENDER_EXCEPTION_HANDLED =
"FullAjaxExceptionHandler: An exception occurred during rendering JSF ajax response."
+ " Error page '%s' will be shown.";
private static final String LOG_RENDER_EXCEPTION_UNHANDLED =
"FullAjaxExceptionHandler: An exception occurred during rendering JSF ajax response."
+ " Error page '%s' CANNOT be shown as response is already committed."
+ " Consider increasing 'javax.faces.FACELETS_BUFFER_SIZE' if it really needs to be handled.";
private static final String LOG_ERROR_PAGE_ERROR =
"FullAjaxExceptionHandler: Well, another exception occurred during rendering error page '%s'."
+ " Trying to render a hardcoded error page now.";
private static final String ERROR_PAGE_ERROR =
""
+ "Error in error Oops!
"
+ "A problem occurred during processing the ajax request. Subsequently, another problem occurred during"
+ " processing the error page which should inform you about that problem.
If you are the responsible"
+ " web developer, it's time to read the server logs about the bug in the error page itself.
"
+ "