Patterns in Software

Preserving JSF Request Parameters and REST URLs

By , 13 February 2013

Preserving JSF Request Parameters and REST URLs

Have you ever used a URL pattern something like this?

http://localhost/app/widgets/WidgetEditor.xhtml?id=300 

or perhaps this?

http://localhost/app/widgets/300/edit 

Well, as a JSF developer you've probably already realized a little problem. How do you remember the id from request to request? There are a few simple solutions.

Preserving JSF Request Parameters and REST URLs
  1. Put the entity in the session scope when you first fetch it and forget the id. This method creates a user session for every request and doesn't support multiple browser tabs.

  2. Put the entity in the view scope when you first fetch it. This method supports multiple tabs but still create a session for every user.

  3. Use plain old HTML hidden input field to remember the id:

    <input type="hidden" name="id" value="${param.id}"/>  

    This works just fine until you start adding other request parameters like ?type=blue&currency=AUD and forget to add the hidden fields to retain them. It's also a weird mix of JSF and HTML.

  4. Add an f:param to the command button. This also works fine as long as you remember to add the parameter to every command button:

    <h:commandButton action="${bean.save}" value="Save">
      <f:param name="id" value="${param.id}"/>
    </h:commandButton>

But there could be a better way.

Your URLs inevitably encode some sort of state, even if that state is simply "what page am I on". Out of the box, JSF got this right. All forms are posted back to the original View ID. But this doesn't tell the whole story because the View ID is not the Request URL and we lose state that is encoded in request parameters or RESTful URL schemes like /app/widgets/300/edit.

We can fix this fairly easily.

All we need to do is post back to the original Request URL instead of the View ID and we're done. The forms come out looking like:

<form action="/app/widgets/WidgetEditor.xhtml?id=300&type=blue">..</form> 

or

<form action="/app/widgets/300/edit">..</form> 

instead of

<form action="/app/widgets/WidgetEditor.xhtml"/>..</form> 

To do this in JSF you need to implement a custom ViewHandler#getActionURL() method that looks like this:

/** 
 * We always post back to the original Request URL, not the viewID
 * since we sometimes encode state in the Request URL such as object id,
 * page number, etc.
 */
@Override
public String getActionURL(FacesContext faces, String viewID) {
    HttpServletRequest request = (HttpServletRequest) 
            faces.getExternalContext().getRequest();

    // remaining on the same view keeps URL state 
    String requestViewID = request.getRequestURI().substring(
            request.getContextPath().length());
    if (requestViewID.equals(viewID)) {

        // keep RESTful URLs and query strings
        String action = (String) request.getAttribute(
                RequestDispatcher.FORWARD_REQUEST_URI);
        if (action == null) {
            action = request.getRequestURI();
        }
        if (request.getQueryString() != null) {
            return action + "?" + request.getQueryString();
        } else {
            return action;
        }
    } else {

        // moving to a new view drops old URL state 
        return super.getActionURL(faces, viewID);
    }
}

Depending on your app, you might like to preserve request parameters across views also.

Your ViewHandler should extend ViewHandlerWrapper and be registered in faces-config.xml like so:

<application>
   <view-handler>com.example.MyViewHandler</view-handler>
</application>

That's it. You can stop worrying about losing the state in your URLs. If you deploy this code you will want to step through it in a debugger to confirm it works correctly for your JSF/webapp configuration.

Have fun.

About Roger Keays

Roger is an active member of the JSF 2 Expert Group and is happy to be a contributor to the Java Community. He has been writing software since the age of 8 and his other interests include languages, science, travel and running.

Comment posted by: Aka aka , last year

 Looks nice! OmniFaces has a form component that works a bit like this as well. See  showcase.omnifaces.org/components/form 

Comment posted by: omid, last year

also you can use seam framework

Comment posted by: Eduard Korenschi, last year

Yes, and because this problem is so frequent, the smart jsf-guy on this plannet, aka BalusC, introduced the o:form targ in Omnifaces (code.google.com/p/omnifaces/) which supports the includeViewParams attribute which does just that. (Besides lots of other useful things).

Comment posted by: Eduard Korenschi, last year

Man, you have to do something about the comments section which is not really visible in the page. After i posted the previous comment i just noticed somebody else hinted about that  ... 2 weeks ago. 

Comment posted by: kolossus, last year

Any particular reason you've chosen JSTL (${}) syntax over EL(#{}) syntax?

Comment posted by: , last year

@kolossus Just habit. Or maybe I subsconciously like the look of all those $$ signs in my code.

@Eduard Sorry about the crappy commenting layout. It is on my TODO list to fix...

The omnifaces approach seems pretty valid although a little less transparent.

Add a comment

Please visit http://www.NinthAvenue.com.au/preserving-jsf-request-parameters-and-rest-urls to add your comments.

Join The Mailing List

Subscribe to our mailing list for the latest news and announcements.

Follow Ninth Avenue

Website Updates