Dealing Gracefully with ViewExpiredException in JSF2 Blog
My previous entry dove under the covers for JSF 2.0 and examinedcomposite component metadata. This one is far less esoteric and shows how to handle the ViewExpiredException
using a new JSF feature, the ExceptionHandler
, contributed byPete Muir a JSF Expert Group representative from JBoss.
JSF throws a ViewExpiredException
when a postback is made to a view and, for whatever reason, that view
cannot be restored. Usually this is due to a session timeout, but a
custom state management solution could also cause this exception to be
thrown. The default behavior in JSF2 is to show the Facelets error page
saying View Expired. The default page is illustrated at right.
One way to fix this is to declare an<error-page>
element in your web.xml, as shown here.
- <error-page>
- <exception-type>javax.faces.application.ViewExpiredException</exception-type>
- <location>/faces/viewExpired.xhtml</location>
- </error-page>
This works well enough. You can even put JSF components on the error page if you put the proper Faces Servlet mapping in the<location>
element, as shown above. If you want to do some application level
manipulation in response to the exception, you'll want something
different, however. In this case, a custom ExceptionHandler
is just the trick. I cover this in much more detial in my upcoming book, JavaServer Faces 2.0: The Complete Reference,
and the example shown in this blog entry is neatly integrated into the
chapter 10 sample app. Consider this blog entry an appetizer. So delete
that old web.xml (it's not needed if you have JSF2 and Servlet 3, which
you get in Glassfish V3) and let's go.
First, we need to install a customExceptionHandler
. This is done using the tried and true JSF decorator pattern. In this case, we place the following into the faces-config.xml
.
There's no annotation for this because it's relatively uncommon, and we
expect advanced users to use the feature. Therefore, the EG tradeoff
was to not add yet another "scan for this annotation" clause to the
spec.
- <factory>
- <exception-handler-factory>com.sun.faces.ViewExpiredExceptionExceptionHandlerFactory</exception-handler-factory>
- </factory>
Here's the code for the class.
- packagecom.sun.faces;
- importjavax.faces.context.ExceptionHandler;
- importjavax.faces.context.ExceptionHandlerFactory;
- publicclassViewExpiredExceptionExceptionHandlerFactory extendsExceptionHandlerFactory {
- privateExceptionHandlerFactory parent;
- publicViewExpiredExceptionExceptionHandlerFactory(ExceptionHandlerFactory parent) {
- this.parent = parent;
- }
- @Override
- public ExceptionHandler getExceptionHandler() {
- ExceptionHandler result = parent.getExceptionHandler();
- result = newViewExpiredExceptionExceptionHandler(result);
- return result;
- }
- }
The interesting things happen on lines 15 - 20. This method is called once per request must return a newExceptionHandler
instance each time it's called. I know the method name should becreateExceptionHandler
, but we stuck with "get" for consistence with other JSF methods. On line 17, we call the real ExceptionHandlerFactory
and ask it to create the instance, which we then wrap in our customViewExpiredExceptionExceptionHandlerFactory
class. This is where the real interesting stuff happens.
- packagecom.sun.faces;
- importjava.util.Iterator;
- importjava.util.Map;
- importjavax.faces.FacesException;
- importjavax.faces.application.NavigationHandler;
- importjavax.faces.application.ViewExpiredException;
- importjavax.faces.component.UIViewRoot;
- importjavax.faces.context.ExceptionHandler;
- importjavax.faces.context.ExceptionHandlerWrapper;
- importjavax.faces.context.FacesContext;
- importjavax.faces.event.ExceptionQueuedEvent;
- importjavax.faces.event.ExceptionQueuedEventContext;
- publicclassViewExpiredExceptionExceptionHandler extendsExceptionHandlerWrapper {
- privateExceptionHandler wrapped;
- publicViewExpiredExceptionExceptionHandler(ExceptionHandler wrapped) {
- this.wrapped = wrapped;
- }
- @Override
- public ExceptionHandler getWrapped() {
- return this.wrapped;
- }
- @Override
- public void handle()throwsFacesException {
- for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator();i.hasNext();){
- ExceptionQueuedEvent event = i.next();
- ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
- if (t instanceofViewExpiredException) {
- ViewExpiredException vee =(ViewExpiredException) t;
- FacesContext fc =FacesContext.getCurrentInstance();
- Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
- NavigationHandler nav =
- fc.getApplication().getNavigationHandler();
- try {
- // Push some useful stuff to the request scope for
- // use in the page
- requestMap.put("currentViewId", vee.getViewId());
- nav.handleNavigation(fc, null, "viewExpired");
- fc.renderResponse();
- } finally {
- i.remove();
- }
- }
- }
- // At this point, the queue will not contain any ViewExpiredEvents.
- // Therefore, let the parent handle them.
- getWrapped().handle();
- }
- }
On line 15, you see we take advantage of thejavax.faces.context.ExceptionHandlerWrapper
convenience class. JSF has lots of these wrapper classes and when you use them, you need only override the getWrapped()
method
to return the instance of the class you're wrapping, which is often
simply passed to the constructor, as shown on lines 19 - 21. Once you
override getWrapped()
, you need only override those methods you're interested in. In this case, we want to override only handle()
, which we do on lines 29 - 57.
We iterate over the unhandler exceptions using the iterator returned fromgetUnhandledExceptionQueuedEvents().iterator()
, as shown on line 30. The ExeceptionQueuedEvent
is aSystemEvent
(also described in detail in my book) from which we can get the actual ViewExpiredException
,
which I do on line 35. I know I'm going to be ultimately showing a JSF
page so I want to extract some information from the exception and place
it in request scope, so I can access it via EL in the page. I do this on
line 37.
On lines 45 and 46, I leverage the JSF
implicit navigation system and cause the server to navigate to the
"viewExpired" page. This assumes, of course, that there is aviewExpired.xhtml
page out there. 2015-08-19:
This also assumes that the exception we are handling is not happening
during the Render Response phase. If the exception is during the Render
Response phase, it is not possible to use the NavigationHandler and you
need to find another approach to convey the desired information.
Naturally, you could parameterize this howevere you like,
context-param, annotation, whatever. Line 46 causes the intervening
lifecycle phases to be skipped.
Note that we have a try-finally block here, and in the finally block, on line 49, we call remove()
on the iterator. This is an important part of the ExceptionHandler
usage
contract. If you handle an exception, you have to remove it from the
list of unhandled exceptions. That way, we know it's safe to call getWrapped().handle()
on line 55.
Finally, let's see my cheesy viewExpired.xhtml
page in action, shown at left.
This page is not much more visually appealing than the default one, but that's not JSF's fault! The one in the JSF2 book will look nicer. Here's the source for this cheesy one.
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:h="http://java.sun.com/jsf/html"
- xmlns:f="http://java.sun.com/jsf/core">
- <h:head>
- </h:head>
- <h:body>
- <h:form>
- <p>To protect your security, we have taken the liberty of logging you
- out. Those who sacrifice liberty for security deserve to have
- their views expired.</p>
- </h:form>
- </h:body>
- </html>
Note that we show the data from the exception on line 15.
This entry showed you how to programmatically intercept theViewExpiredException
and do something nice with it. If you have any other state that you can
show in the page, it's easy to include it in the Facelets view.
Technorati Tags: edburns
17 Comments
-
this not work with jboss richfaces in glassfish WARNING: ApplicationDispatcher[/uniplat] PWC1231: Servlet.service() for servlet Faces Servlet threw exception java.lang.NullPointerException at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:119) at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110) at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118) at javax.faces.webapp.FacesServlet.service(FacesServlet.java:312) at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523) at org.apache.catalina.core.ApplicationDispatcher.doInvoke(ApplicationDispatcher.java:822) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:684) at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:519) at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:488) at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:379) at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:485) at org.apache.catalina.core.StandardHostValve.dispatchToErrorPage(StandardHostValve.java:679) at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:311) at org.apache.catalina.core.StandardHostValve.postInvoke(StandardHostValve.java:241) at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:334) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:233) at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165) at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791) at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693) at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954) at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170) at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88) at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76) at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53) at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) at com.sun.grizzly.ContextTask.run(ContextTask.java:69) at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330) at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309) at java.lang.Thread.run(Thread.java:619) //
-
Hi, What is the recommended way of handling wrapped exceptions in JSF 2? I tried throwing an intentional NPE from an EJB and it ended up wrapped in EvaluationException/EJBTransactionRolledbackException/etc. Tried using the getRootCause() but even that didn't get me the NPE I threw. -Nik //
-
But seriously, this can be a tricky problem. I recently discovered and fixed a case where an exception was thrown and swallowed, obscuring the true problem.
It's possible my recent checkin may make your problem go away.
Please try a nightly build within the last week.
If you can't do that, please try to post a stacktrace here and we'll see what can be done.
Thanks,
Ed
//-
-
-
For the example the exception if a FacesException and getCause reveals the NPE. OTOH, the scenario is not quite the same since there was EJB + CDI in my stack so the issue might be there. Perhaps the EJB exception thrown should be considered the root cause from the perspective of JSF. In any case, a one-liner Throwable getTrueRootCause(Throwable) with some recursion will dig out the root cause for most cases. //
-
-
-
-
-
I've played around a bit with the version that is deployed with Mojarra 2.0.4. Unfortunately it doesn't seem to work properly when you are changing locales programmatically. The handler is triggered on:
FacesContext.getCurrentInstance().getViewRoot().setLocale(currentLocale);
A sample could be found at www.coreservlets.com/JSF-Tutorial/jsf2/#Events
// -
Hi!
I'm attempting to adapt this example in my application but encounter this error in the call: nav.handleNavigation(fc, null, "viewExpired");
java.lang.NullPointerException
at org.apache.myfaces.application.NavigationHandlerImpl.getNavigationCase(NavigationHandlerImpl.java:203)
at org.apache.myfaces.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:77)Line 203: String viewId = facesContext.getViewRoot().getViewId();
getViewRoot() returns null for some reason. Any ideas as to how I can fix this problem?
Appreciate any help. :)
UPDATE: Of course I found a workaround as soon as I posted the question. I perform the following before calling the navigate-method. if ( fc.getViewRoot() == null ) { UIViewRoot view = fc.getApplication().getViewHandler().createView( fc, vee.getViewId() ); fc.setViewRoot( view ); } Perhaps you can explain what happened to the view root. ;)
// -
Nice article Ed.
However, I find this part of the JSF API ugly. No offense to its designers but how would anyone know that you're going to need a constructor that accepts ExceptionHandlerFactory instance as an argument? They could have made an abstract method to set that instance variable. No one knows until somebody like you posts something like this or read the source code.
Hope to hear from you soon.
// -
Hi Ed
Im having an unexpected problem with the ExceptionHandlerWrapper.
Before the ExceptionHandlerWrapper handle the exception, the error is printed on the console. I do not want the exception apears inside the console. Something is intercepting and loggin it before entering the ExceptionHandlerWrapper
I cannot find anyreference on this "issue".
Any ideas ?
Excluding this "problem", the ExceptionHandlerWrapper works perfectly.
Thanks
// -
getUnhandledExceptionQueuedEvents return an empty collection in case of ajax requests. How should I proceed?
// -
I have successfully and Gracefully dealt with ViewExpiredException in JSF2. Good solution. //
-
Just applied your code into my new HR application. Works like a charme!
Thanks.
Bruno
seltzerl Sep 17, 2009 3:56 AM