You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
A web application can run into a server-side error for many different reasons and at any point in time. Finding the right strategy for your application, how to deal with these error conditions and exceptional situations, can be difficult. This page provides some guidelines and a simple but effective solution.
When a client request is handled by a web application server, exceptions can be thrown by code at any time. Typically, you need to consider:
Seam comes with an exception handling servlet filter that wraps its processing of a request:
<web:exception-filter url-pattern="*.seam"/>
Whenever an exception occurs while a request is being processed, this filter will catch it and try to do map it to an outcome. Your metadata for this is typically in a single global pages.xml file:
<?xml version="1.0" encoding="UTF-8"?> <pages xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd"> <!-- All exceptions we can gracefully recover from, usually by redirecting to an application page or if that is not an option, sending an HTTP error code that is not '500' (which would be caught in web.xml). --> <!-- Thrown when a @Restrict action is executed and the user is not logged in --> <exception class="org.jboss.seam.security.NotLoggedInException" log="false"> <redirect view-id="/login.xhtml"/> </exception> <!-- Throw when a JSF form is submitted but the view-id can not be found in the HTTP session --> <exception class="javax.faces.application.ViewExpiredException" log="false"> <redirect view-id="/home.xhtml"> <message severity="WARN">Session timeout, you have been redirected to the start page.</message> </redirect> </exception> <!-- Thrown by either the Seam entity framework or when using JPA directly --> <exception class="org.jboss.seam.framework.EntityNotFoundException" log="false"> <http-error error-code="404"/> </exception> <exception class="javax.persistence.EntityNotFoundException" log="false"> <http-error error-code="404"/> </exception> </pages>
As you can see, some of these exceptions will occur frequently. The EntityNotFoundException for example is a very natural outcome when a referenced entity (like a customer or product) is no longer available in the database. Instead of showing an error or application page with a message, an HTTP 404 status code is the right response - this tells search engines to invalidate the requested URL in their index.
An exception that indicates that a user has to be logged in, for example, is best handled by showing the user the login page instead of an error message.
In general, you also do not want these exceptions and their stacktraces to be logged on the server, as there is nothing you can do about them.
NOTE: The JSF reference implementation and Facelets have many problems with exception handling. In fact, the JSF RI even swallows exceptions in certain situations and some of this behavior is even required by the JSF 1.0 specification. Both frameworks also log exceptions, including their stacktrace, at a WARN level, and then pass the exception further up the call stack (into Seam usually), where the exception might get logged again. If you don't handle it in Seam, it will get logged again by your servlet container. All of this is very disappointing but there is nothing Seam can do. Although lowering the log level to ERROR for a few JSF and Facelets categories helps in production, we do not recommend this in development. For example, the JSF RI also reports a problem with the status message queue (e.g. if you forget <h:messages/> on a page and there are queued message) with a WARN message. You will not see such programming errors if your log level during development is ERROR. In other words: Even though you use log="false" in Seam's pages.xml configuration, you will sometimes see stacktraces printed by JSF and Facelets.
What happens when a runtime exception is thrown and it's not listed in pages.xml?
Many Seam applications try to handle unrecoverable exceptions with a generic catchall
in pages.xml. An <exception> declaration without an exception type is used by Seam as the fallback when no other type matches. Although this works great at first, it's not sufficient for production systems.
The only non-recoverable runtime exceptions you would catch are those that occur within the Seam request call stack casas apostas. Those might of course include exceptions thrown by Hibernate, for example. But there are other fatal runtime exceptions that might be thrown, usually by the servlet container outside of Seam request processing stack. Because these exceptions do not occur within the Seam exception filter, they never appear inside Seam and can not be handled through pages.xml.
So, you can of course map a catchall in pages.xml but you will still have to consider exceptions outside of Seam. These are handled in web.xml. In fact, I do not recommend having any catchall in pages.xml and just handle all of the unexpected errors in web.xml. That way, you have one less thing to worry about. Also, when an exception which you did not expect occurs within the Seam call stack, are you sure that Seam (or JSF, or Facelets, or Hibernate) is still in a state that allows you to handle the situation gracefully? If a Facelets exception occured, can you still render an error page with Facelets? It is best to fall back to the most trivial environment in such a situation, without relying on too many subsystems for a robust exception handling procedure.
In the pages.xml example above you have already seen the hint that error code 500 would be caught in web.xml
. When an exception is thrown inside the Seam call stack, and the Seam filters do not handle it, it is passed on to the servlet container wrapped in a generic ServletException. The servlet container then looks for the exception type (also using the root cause, your exception) in the handlers configured in web.xml. If no handler can be found for the type, a 500 status code is created and the web.xml handlers are checked again, this time with the numeric code. You also need to consider situations where a servlet (including your Seam application!) might return a status code. For example, the 404 NOT FOUND we mapped earlier is also passing through the web.xml handlers.
Therefore, you should at least have the following mapping defined in web.xml:
<error-page> <error-code>500</error-code> <location>/WEB-INF/pages/applicationerror.jsp</location> </error-page>
This is the ultimate catch-all. It will catch any exception thrown by any servlet or filter (hence, all exceptions in your Seam application) and it will also catch any exception thrown by the servlet container itself. We use a global generic error page to tell the user of the application what has happened and what to do next.
Unfortunately, there are some exceptions thrown by the servlet container that you might want to handle special. One in particular, which occurs when your servlet container hits the maximum session limit, should not be handled with a JSP page. When you are running out of space for user sessions, nothing works anymore (at least in Tomcat) and you need to abort the current request quickly. It's best to just send a simple HTML page to the client, informing the user to try again later. Because Tomcat is an awful production environment, nobody ever thought about this problem before and the only way to even be informed of this situation is by catching a generic exception:
<!-- Don't use a Faces or JSP page here, too many sessions means nothing works anymore! Unfortunately, Tomcat can't be bothered to throw anything better than an IllegalStateException... So in other words: If your application throws an IllegalStateException, this page will be shown, not the 'applicationerror.jsp' page declared above. --> <error-page> <exception-type>java.lang.IllegalStateException</exception-type> <location>/WEB-INF/pages/toomanysessions.html</location> </error-page>
So if your application throws an uncaught IllegalStateException, this configuration would now consider this to be an out of memory for new sessions
error.
The applicationerror.jsp page defined above deserves some extra attention. The following example works great in practice:
<%@ page import="java.io.PrintWriter" %> <%@ page import="common.util.Functions" %> <html> <head> <meta HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=UTF-8"/> <title>Application Error</title> </head> <body> <style> body { font-family: arial, verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 1.1em; } .errorHeader { font-size: 1.6em; background-color: #6392C6; color: white; font-weight: bold; padding: 3px; margin-bottom: 10px; } .errorFooter { font-size: 0.8em; background-color: #6392C6; color: white; font-style: italic; padding: 3px; margin-top: 5px; } .errorMessage { color: red; font-weight: bold; } .errorExceptionCause { padding: 3px; border-style: solid; border-width: 1px; border-color: #9F9F9F; background-color: #E0E0E0; font-size: 80%; } </style> <div class="errorHeader">Your request was not successful, server-side error encountered:</div> <% String customMessage = // ... Some user-friendly message Boolean showstack = // ... Do you want to show a stacktrace? if (customMessage != null) { %><span class="errorMessage"><%=customMessage%></span><% } Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception"); if ((showstack == null || showstack) && exception != null) { Throwable rootCause = Functions.unwrap(exception); String exceptionMessage = rootCause.getMessage(); %><hr/>Root cause exception message: <span class="errorMessage"><%=exceptionMessage%></span><% %><br/><% %><span id="errorDetails" class="errorExceptions"><% %><pre class="errorExceptionCause"><% PrintWriter pw = new PrintWriter(out); rootCause.printStackTrace(pw); %></pre><% %></span><% } %> <div class="errorFooter"><%= new java.util.Date() %></div> </body> </html>
Note that this example does not use the isErrorPage=true directive! It is simply not necessary to access the implicit exception instance, we obtain the exception instance through the javax.servlet.error.exception request attribute. You can externalize (to servlet context properties?) whatever custom message you want to show your users (Contact the administrator, here is the phone number...
) and if you want to show them (bet at home) an exception stacktrace (true in beta testing, false in production, etc).
Finally, note that when you enable Seam debugging (with seam-debug.jar on your classpath), Seam will handle reachable
exceptions internally and automatically redirect to the debug page. Disable debugging to see the shown configuration in action.