Error handling for MyFaces Core 2.0 and later versions
...
Code Block |
---|
|
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0">
<!-- ... -->
<factory>
<!-- ... -->
<exception-handler-factory>org.apache.myfaces.context.ExceptionHandlerFactoryImpl</exception-handler-factory>
<!-- ... -->
</factory>
<!-- ... -->
</faces-config>
|
...
Code Block |
---|
title | ExceptionHandlerFactoryImpl.java |
---|
|
public class ExceptionHandlerFactoryImpl extends ExceptionHandlerFactory
{
@Override
public ExceptionHandler getExceptionHandler()
{
return new SwitchAjaxExceptionHandlerWrapperImpl(
new MyFacesExceptionHandlerWrapperImpl(new ExceptionHandlerImpl()) ,
new AjaxExceptionHandlerImpl());
}
}
|
...
Code Block |
---|
title | ExceptionHandlerFactoryImpl.java |
---|
|
public class ExceptionHandlerFactoryImpl extends ExceptionHandlerFactory
{
@Override
public ExceptionHandler getExceptionHandler()
{
return new CustomExceptionHandlerWrapper(getWrapped().getExceptionHandler());
}
}
|
...
Code Block |
---|
title | MyFacesExceptionHandlerWrapperImpl.java |
---|
|
public class MyFacesExceptionHandlerWrapperImpl extends ExceptionHandlerWrapper
{
//...
public void handle() throws FacesException
{
//... some custom code goes here ...
}
}
|
...
MyFaces Core provide a custom ExceptionHandler to deal with exceptions and provide detailed information about it. Since 2.0.8/2.1.2 this is disabled on Production environments unless it enabled on web.xml file. Don't forget to provide your custom error page in this scenario, to prevent show more information than necessary.
Code Block |
---|
|
<context-param>
<param-name>org.apache.myfaces.ERROR_HANDLING</param-name>
<param-value>true</param-value>
</context-param>
<!-- if you want to use a different resource template file than
"META-INF/rsc/myfaces-dev-error.xml" this param let you configure
it. (since 1.2.4-SNAPSHOT and 1.1.6-SNAPSHOT)-->
<context-param>
<param-name>org.apache.myfaces.ERROR_TEMPLATE_RESOURCE</param-name>
<param-value>META-INF/rsc/custom-dev-error.xml</param-value>
</context-param>
|
Provide an error page
The default ExceptionHandler in Production stage or when myfaces error handling is disabled just throw If org.apache.myfaces.ERROR_HANDLING is set to "false", or if it is undefined and the project stage is "Development", the default ExceptionHandler just throws an exception. So you can still setup an error page by adding something like this in your web.xml file:
Code Block |
---|
|
<error-page>
<error-code>500</error-code>
<location>/somepage.jsp</location>
</error-page>
|
...
If this is not what you want, though, you can always disable or modify this error-handling with the following parameters:
Code Block |
---|
|
<!-- 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 use a different resource template file than
"META-INF/rsc/myfaces-dev-error.xml" this param let you configure
it. (since 1.2.4-SNAPSHOT and 1.1.6-SNAPSHOT)-->
<context-param>
<param-name>org.apache.myfaces.ERROR_TEMPLATE_RESOURCE</param-name>
<param-value>META-INF/rsc/custom-dev-error.xml</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>
|
...
For define a custom template file:
Code Block |
---|
|
<context-param>
<param-name>org.apache.myfaces.ERROR_HANDLING</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>org.apache.myfaces.ERROR_TEMPLATE_RESOURCE</param-name>
<param-value>META-INF/rsc/mycustom-template-error.xml</param-value>
</context-param>
|
...
The info of the error in the jsf page can be found using:
Code Block |
---|
#{exceptionContext.cause} : Cause retrieved from the exception
#{exceptionContext.stackTrace} : Stack trace of the exception
#{exceptionContext.exception} : Exception handled by this page
#{exceptionContext.tree} : Print the component tree of the page that cause the error
#{exceptionContext.vars} : Enviroment variables from the request
|
...
1 define an error handling web page in web.xml
Code Block |
---|
|
<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.
Code Block |
---|
|
<!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>
|
...
Code Block |
---|
|
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
Code Block |
---|
|
[1] List exceptions = ExceptionUtils.getExceptions(exception);
[2] Throwable throwable = (Throwable) exceptions.get(exceptions.size()-1);
[3] String exceptionMessage = ExceptionUtils.getExceptionMessage(exceptions);
|
...
So the new fillStackTrace become
Code Block |
---|
|
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<exceptions.size(); i++)
{
if (i > 0)
{
pw.println("Cause:");
}
throwable.printStackTrace(pw);
}
}
|
...
Another way to handle this would be to use an intermediate step by specifying a non-JSF URL as the error page and then somehow redirecting to the JSF error page. For example, for the 404 error code you could specify ''/error/404_redirect.html'':
Code Block |
---|
|
<html><head><meta http-equiv="Refresh" content="0; URL=/DPSV4/error/404.jsf"></head></html>
|
...
Code Block |
---|
title | RedirectServlet.java |
---|
|
public class RedirectServlet extends HttpServlet {
private static final String URL_PREFIX = "url=";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String query = req.getQueryString();
if ( query.contains(URL_PREFIX) ) {
String url = query.replace(URL_PREFIX, "");
if ( !url.startsWith(req.getContextPath()) ) {
url = req.getContextPath() + url;
}
resp.sendRedirect(url);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
|
used like this in ''web.xml'':
Code Block |
---|
|
<servlet>
<servlet-name>Redirect Servlet</servlet-name>
<servlet-class>ca.gc.agr.ops.web.jsf.redirect.RedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Redirect Servlet</servlet-name>
<url-pattern>/redirect</url-pattern>
</servlet-mapping>
<error-page>
<error-code>403</error-code>
<location>/redirect?url=/error/403.jsf</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/redirect?url=/error/404.jsf</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/redirect?url=/error/500.jsf</location>
</error-page>
|
...
with this web.xml configuration
Code Block |
---|
|
<error-page>
<error-code>500</error-code>
<location>/error.jsp</location>
</error-page>
|
...
A fragment of your dependency plan would be like this:
Code Block |
---|
|
<dep:dependencies>
.
.
<dep:dependency>
<dep:groupId>org.apache.myfaces.core</dep:groupId>
<dep:artifactId>myfaces-api</dep:artifactId>
<dep:type>jar</dep:type>
</dep:dependency>
<dep:dependency>
<dep:groupId>org.apache.myfaces.core</dep:groupId>
<dep:artifactId>myfaces-impl</dep:artifactId>
<dep:type>jar</dep:type>
</dep:dependency>
.
.
</dep:dependencies>
<dep:hidden-classes>
<dep:filter>javax.faces</dep:filter>
<dep:filter>org.apache.myfaces</dep:filter>
</dep:hidden-classes>
|
This solution was tested under Apache Geronimo 2.1.3., but will probably be similar in other versions.