Handling Server Errors
MyFaces, from version 1.2.1 and 1.1.7, includes automatic error-handling for the full JSF-Lifecycle (taken over mostly from Facelets, with a few adoptions and additions). So for most projects during development, you will have exactly what you want with these new error-handling possibilities.
If this is not what you want, though, you can always disable or modify this error-handling with the following parameters:
<!-- if you want to disable the behaviour completely -->
<context-param>
<param-name>org.apache.myfaces.ERROR_HANDLING</param-name>
<param-value>false</param-value>
</context-param>
<!-- if you are using myfaces + facelets don't forget to do this -->
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>false</param-value>
</context-param>
<!-- if you want to choose a different class for handling the exception - the error-handler needs to include a method handleException(FacesContext fc, Exception ex)-->
<context-param>
<param-name>org.apache.myfaces.ERROR_HANDLER</param-name>
<param-value>my.project.ErrorHandler</param-value>
</context-param>
If you do this, you can now read on to get to general ways of handling server-errors.
Server errors such as HTTP 500 can occur for a number of reasons such as uncaught exceptions, missing JSFs or backing beans, bad URL and the list goes on. While we hope these only occur during development it is important to plan to catch and deal with these errors gracefully when running live with multiple users.
Several approaches have been discussed on the mailing list:
Using servlets
Mert Caliskan (
http://www.jroller.com/page/mert?entry=handling_errors_with_an_errror) describes an approach which wraps the JSF servlet with a new servlet which delegates to the faces servlet but handles uncaught exceptions allowing the developer to redirect to a custom error page.
With JSF
Another approach is described in the book 'Core Server Faces' which uses the servlet engine to catch the error and delegate to a JSF error page. While not as elegant as the first approach this method has several advantages for some users
[1] It uses a standard JSP approach and framework
[2] It does not require custom servlets
[3] It can easily be customized/changed by simply changing the error handler definition in the web.xml
To implement this method perform the following steps
[1] define an error handling web page in web.xml
<error-page>
<error-code>500</error-code>
<location>/ErrorDisplay.jsf</location>
</error-page>
[2] Create the error handler display. Due to a problem with the JSF 1.1 specification, the error handling page cannot use a <f:view> but must use a subview.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<f:subview id="error"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:h="http://java.sun.com/jsf/html">
<html>
<head>
<meta content="no-cache" http-equiv="Cache-Control" />
<meta content="no-cache" http-equiv="Pragma" />
<title>CMS - Error</title>
<t:stylesheet path="#{SessionBean.styleSheet}" />
</head>
<body>
<h:form>
:
: set up the normal view
:
<h:outputText styleClass="infoMessage" escape="false" value="#{ErrorDisplay.infoMessage}" />
<t:htmlTag value="br" />
<h:inputTextarea style="width: 99%;" rows="10" readonly="true" value="#{ErrorDisplay.stackTrace}" />
:
: more view stuff
:
</h:form>
</body>
</html>
</f:subview>
[3] Create a backing bean in request scope which can process the error.
import cms.beans.framework.AbstractUIBean;
import com.c2gl.jsf.framework.ApplicationResource;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.servlet.ServletException;
public class ErrorDisplay extends AbstractUIBean {
private static final long serialVersionUID = 3123969847287207137L;
private static final String BEAN_NAME = ErrorDisplay.class.getName();
public String getInfoMessage() {
return "An unexpected processing error has occurred. Please cut and paste the following information" + " into an email and send it to <b>" +
some email address + "</b>. If this error " + "continues to occur please contact our technical support staff at <b>" +
some phone number etc + "</b>.";
}
public String getStackTrace() {
FacesContext context = FacesContext.getCurrentInstance();
Map requestMap = context.getExternalContext().getRequestMap();
Throwable ex = (Throwable) requestMap.get("javax.servlet.error.exception");
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
fillStackTrace(ex, pw);
return writer.toString();
}
private void fillStackTrace(Throwable ex, PrintWriter pw) {
if (null == ex) {
return;
}
ex.printStackTrace(pw);
if (ex instanceof ServletException) {
Throwable cause = ((ServletException) ex).getRootCause();
if (null != cause) {
pw.println("Root Cause:");
fillStackTrace(cause, pw);
}
} else {
Throwable cause = ex.getCause();
if (null != cause) {
pw.println("Cause:");
fillStackTrace(cause, pw);
}
}
}
}
Also have a look at our ExceptionUtils class. It encapsulates the way how to get the real root cause
[1] List exceptions = ExceptionUtils.getExceptions(exception);
[2] Throwable throwable = (Throwable) exceptions.get(exceptions.size()-1);
[3] String exceptionMessage = ExceptionUtils.getExceptionMessage(exceptions);
[1] get a list of all exceptions - using getRootCause if available or getCause
[2] get the initial exception
[3] get the first exception with an message starting with the initial exception
So the new fillStackTrace become
private void fillStackTrace(Throwable ex, PrintWriter pw)
{
if (null == ex) {
return;
}
List exceptions = ExceptionUtils.getExceptions(exception);
Throwable throwable = (Throwable) exceptions.get(exceptions.size()-1);
for (int i = 0; i<execptions.size(); i++)
{
if (i > 0)
{
pw.println("Cause:");
}
throwable.printStackTrace(pw);
}
}
In the backing bean we construct a message which informs the user that something we didnt't plan for has happened with directions on who to call, what to do etc. We don't really care if they actually do cut-and-paste the error and email it to us as it is also in Tomcat's logs but giving the user something to do and become part of resolving the problem is always a good idea
With plain JSP
If you didn't require any JSF functionality in your JSP page it might be worth to consider using the following error.jsp
with this web.xml configuration
<error-page>
<error-code>500</error-code>
<location>/error.jsp</location>
</error-page>
This reduces the number of technologies required to show the error page which might improve the availablity of this page