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

Preserving JSF Request Parameters and REST URLs

Roger Keays is an artist, an engineer, and a student of life. He has no fixed address and has left footprints on 40-something different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, the proper use of semicolons, and finding good food.

Leave a Comment

Please visit https://rogerkeays.com/preserving-jsf-request-parameters-and-rest-urls to add your comments.

Comment posted by: , 11 years ago

@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.

Comment posted by: kolossus, 11 years ago

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

Comment posted by: Eduard Korenschi, 11 years ago

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: Eduard Korenschi, 11 years ago

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: omid, 11 years ago

also you can use seam framework

Comment posted by: Aka aka , 11 years ago

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