Thoughts on…

Java Middleware & Systems Management

JSF Odyssey – ViewExpiredException

with 9 comments

For any developer that has used JSF for even a week, you know exactly what the title of this entry is referring to.

A quick google search for “ViewExpiredException” finds users complaining about this on threads from all different sorts of vendors: Oracle, Apache, IceFaces, MyFaces, JBoss. And that’s just the first 10 entries! But the fault does not lie with any of these vendors. The dreaded ViewExpiredException is most likely the result of the fact that you’re interacting with (or developing) a user-authenticated, web-based application written in JSF. If you’re logged in and your HTTP session expires, JSF will quite annoyingly throw ViewExpiredException during the RESTORE_VIEW phase of the JSF life cycle – it’s a feature!

During my wanderings I’ve seen people try to solve this issue in a variety of ways. Some try to use global Exception handling in the web.xml file for their WAR. Others try to use a pure JSF-based solution whereby a PhaseListener can attempt to perform a response redirect during the RESTORE_VIEW phase. Even JBoss Seam tries to solve this in their own way using their exception handlers in the components.xml file.

However, none of these solutions are complete in my opinion. All of them seem to ignore one completely obvious question – how do I redirect the user back to the LAST page they were on before the session timed out? Without this piece, the usability of the site goes way down and, as most of these solutions will proffer, the user has to start at the site’s main entry point again.

Restoring the last visited JSF view, however, is easier said than done. The HTTP POST-based nature of the framework makes it very difficult to capture the needed state / context prior to the timeout. It doesn’t really matter if you record the last accessed path if you don’t have the appropriate URL parameters to pass to it after the user logs back in. So, the only proper fix for this is to extend JSF in some way to provide RESTful site interactions.

Luckily, there are frameworks which (to different degrees) provide RESTful concepts, such as JBoss Seam with bookmarkable URLs or RestFaces with the enablement of HTTP GET in your JSF environment. Unfortunately, the RHQ Project needed one of these solutions in early ’06, and those frameworks either didn’t exist or were still being incubated at the time. So, necessity being the father of invention, we wrote our own mechanism. It can be summarized as follows:

1) Redirects across all form submission boundaries
2) Custom JSF navigation handler that performs EL resolution of “enhanced” viewIds

With it, a standard JSF navigation rule then looks like:

<navigation-rule>
   <from-view-id>/some/path/from/page.xhtml</from-view-id>
   <navigation-case>
      <from-outcome>outcome1</from-outcome>
      <to-view-id>/some/path/to/page.xhtml?
         p1=#{param.p1}&p2=#{param.p2}</to-view-id>
      <redirect/>
   </navigation-case>
</navigation-rule>

Granted, the syntax is not all that inviting, but it gets the job done. Nearly every single page flow in RHQ starts and ends with a URL specifying representational state explicitly in its parameter map. And from a developer's perspective, the slightly modified navigation rule is likely the only modification that has to be made. Most of the time the visible form elements (on the form leading up to the enhanced navigation rule) will be a superset of the state that will be resolved, in which case you're done. However, if you need to propagate additional state, you can always include an unlimited number of hidden form inputs to submit your extra context information.

The bulk of the logic for how this all works is hidden behind various extensions that were made to the JSF framework. Since we're crossing redirect boundaries, any errors that your managed bean would have placed in the FacesContext would normally be lost. But this was taken into consideration and is handled in a 3-step process by a custom phase listener:

* Step 1: capture any messages put in the GLOBAL faces messages context, and save them to the session at the end of biz tier processing
* Step 2: before the processing continues on the other side of the redirect boundary, copy the save elements back into the GLOBAL faces context
* Step 3: clear out the area of the session used for this message propagation

For those that understand better by seeing the code:

public class FacesMessagePropogationPhaseListener 
implements PhaseListener {
    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }
    public void beforePhase(PhaseEvent event) {
        if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
            List savedMessages = 
               removeGlobalFacesMessagesFromSession();
            // step2: retrieve 'em
            putGlobalFacesMessagesInFacesContext(savedMessages); 
        }
    }
    public void afterPhase(PhaseEvent event) {
        if (event.getPhaseId() == PhaseId.INVOKE_APPLICATION) {
            // step1: save 'em
            putGlobalFacesMessagesInSession(); 
        } else if (phaseId == PhaseId.RENDER_RESPONSE) {
            // step3: delete 'em
            removeGlobalFacesMessagesFromSession(); 
        }
    }
}

The only thing left to do is to override the standard navigation handler with one performs variable resolution:

public class FaceletRedirectionViewHandler 
extends FaceletViewHandler {
    @Override
    // Evaluate any EL variables contained in the view id.
    public String getActionURL(FacesContext facesContext, 
    String viewId) {
        FacesContext facesContext = 
            FacesContext.getCurrentInstance();
        ELContext elContext = facesContext.getELContext();
        ExpressionFactory factory = 
            facesContext.getApplication().getExpressionFactory();
        ValueExpression valueExpression = 
            factory.createValueExpression(elContext, 
                viewId, String.class);
        String resolvedActionURL = 
            (String) valueExpression.getValue(elContext);
        return resolvedActionURL;
    }
}

What was this blog about again? Oh, yes, that's right - ViewExpiredException. Although this mechanism concerning URL resolution might have seemed like a tangent to the problem at hand, it was necessary groundwork that had to be laid, which now gives us the tools to undo the damage caused by a lifecycle "feature" of our component framework. As you navigate around the site now, your URL history will look something like:

/some/path.xhtml?p1=value1
/some/other/path.xhtml?p2=value2&p3=value3
...
/still/some/other/path.xhtml?p4=value4

All you have to do is record the last URL the user was on; if at any point the ViewExpiredException occurs, simply redirect back to that last URL. The RHQ platform uses standard error handling in the web.xml to forward to a JSP page that then has the logic to call and redirect the response writer to this saved value. If you rely on the browser's history as the storage mechanism, then recalling that last URL only takes one line of javascript:

history.go(-1)

Note: you may have to use either -1 or -2 as the argument to the 'go' method, depending on whether the user was attempting to access a secured page (which might have redirected to some authenticating servlet before redirecting you back to the requested URL where the ViewExpiredException was thrown from)

Using the browser's history also has the advantage of operating quite naturally in a multi-window (or multi-tab) environment. If you want your users to be able to navigate around your application using multiple windows, the browser history will provide natural isolation of these individual browsing histories. This way, regardless of which tab the user is on when they experience the ViewExpiredException, they are redirected back to the last page they are on *within that window* (as opposed to the last accessed page in any of that user's open windows).

If it wasn't already apparent, the key factor driving this solution is the user experience. Anyone can get away with "forgetting" where a user was and simply redirecting to an entry point or login page upon receiving the ViewExpiredException. Though that's easy to implement, it's not exactly the most appealing functionality from the end user's perspective. This solution tries to make your application as attractive as possible my minimizing the distractions. Even though it requires a little bit more work on the part of the developer, it goes a long way towards providing an intuitive experience to your users.

---

On the one hand, it's a shame that the JSF spec didn't have standard mechanisms for handling this extremely common use case. On the other hand, if it was the spec writers intention to keep us developers in business, they've done an excellent job. Time will tell whether JSF 2 will obviate the need for this custom solution.

About these ads

Written by josephmarques

February 5, 2009 at 1:29 pm

Posted in webdev

Tagged with

9 Responses

Subscribe to comments with RSS.

  1. very very interesting…
    some more detailed explaination would be nice.
    lets say on the : getActionURL() method.
    the reason why make this method.
    and what it does mainly.

    also : “The RHQ platform uses standard error handling in the web.xml to forward to a JSP page… ”
    the sample of this.

    or may be some files names to look at in RHQ, now that this code is open source ?

    Thanks a lot.
    Antoine
    JBoss Portal / JSF / Richfaces consultant.

    Antoine_h

    February 19, 2009 at 11:47 pm

  2. Antoine,

    Notice that the target of my *redirected* navigation-rule was:

    /some/path/to/page.xhtml?p1=#{param.p1}&p2=#{param.p2}

    Thus, the p1 and p2 parameters need to be resolved, which is why I overrode the standard FaceletViewHandler with one that evaluates expressions and then performs the navigation.

    Remember: by redirecting navigation-rules, I ensure that the history looks like a bunch of GET requests (instead of standard JSF postbacks). This is what enables me to safely go backwards in the history upon session timeout without danger of the user seeing repost warnings and, worse, re-executing partial web flows that could cause any number of undesired effects.

    —–

    RHQ has been open source since early 2008. All of the code examples I used are distilled snippets of the real source. All file names I used are real. Take a look at the maven module that contains all of the UI code (/modules/enterprise/gui/portal-war) for more information.

    josephmarques

    February 21, 2009 at 6:17 pm

  3. Understood…
    I should look better also… ;-)

    I will try this.
    and may be also try with some RestFaces.
    Did someone yet check that it is working ?

    I wonder what may come up with the use of Portal and JBoss Portlet Bridge, that manipulates also the URL.

    With RichFaces, I guess it should go.
    But, I wonder if some parameters may not need to be retrieved also from the RichFaces components.

    Such as the rich:datascroller : the current page param may be interesting to have when retrieving the view.

    This may make a lot of param to set in the navigation rules… but for the user experience point of view, it may worth try this.

    Thanks,
    Antoine
    JBoss Portal / JSF / Richfaces consultant.

    Antoine_h

    February 21, 2009 at 11:36 pm

  4. There are definitely many ways to achieve the same goal here. I will not claim RHQ’s now 2-year-old solution is ideal, or even the best approach given the technology available today. However, at the time, it was the fastest way to get around the headaches caused by pure JSF, and get a working application out the door.

    In retrospect, I’m most critical of the XML hell that the current solution imposes on the developer. If I had to do it all over again today, I probably would have used Seam’s Redirect object (in the org.jboss.seam.faces package) to achieve the same effect.

    It’s a solution that has the same intention as the one discussed in this blog, but is much easier to use (and learn) since it let’s you specify when to redirect, which parameters to pass, and where to go *all* from within your managed component.

    Remember, the redirect trick is just a means to an end. As long as you somehow ensure that you’re only recording GET requests in the history, you can use the remainder of the solution discussed in this blog to provide your users a smooth interaction and natural flow when their sessions times out.

    josephmarques

    February 22, 2009 at 12:48 am

  5. yes.
    keep the idea and see how to achieve it.

    I’ve looked at it further, and the conclusion is inverse than the one I first thought.

    about the Portal : it should be ok. just make sure the url manipulation can be done with no collision by both JSF and the portal.

    but with any ajax request that involves the state of the page/navigation (datascroller, tabPanel…) it won’t work !
    hence not with RichFaces.
    As the request is sent to the server without notifying the browser history.

    may be that params can be retrieved with Seam’s Redirect object or RestFaces.

    There is something in the RichFaces live demo :
    http://livedemo.exadel.com/richfaces-demo/richfaces/dataLists.jsf?tab=info&cid=1068726

    the navigation parameters are in the url. Hence the navigation is recorded in the browser history.
    This is a example that should be interesting to look at also.

    a lot of work ahead…

    Many thanks for this post and answers,
    Antoine

    Antoine_h

    February 22, 2009 at 2:35 am

  6. Antoine,

    Partial page requests can be finicky sometimes. I believe it should be up to the framework that you rely on to properly “talk” to the browser history and insert the appropriate entries, even for AJAX-initiated requests.

    I know for a fact that ajax4jsf (the underlying library for Richfaces) does NOT do this, and people tend to open JIRAs against RHQ as a result of it. ;/

    Perhaps the Richfaces guys should take a page from the book of GWT, specifically how their AJAX stuff can manipulate the client side history to allow proper backward navigation through the DOM changes, just as if they had been full-page requests.

    Good luck.

    josephmarques

    February 22, 2009 at 4:09 am

  7. Here’s what we have in JSF2. Do you think it’s any better? http://bit.ly/CMmME

    Ed Burns

    September 4, 2009 at 5:30 pm

  8. [...] “classic” ViewExpiredException. I discuss the details of how this mechanism works in my other post on [...]

  9. I’d just like to know WHICH view is expiring. A line number. A DOM id. ANY info to help debug. An error with no context is so useless. I get this immediately upon subject social security number search in my system.

    Clint

    May 10, 2013 at 3:59 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: