Preserving JSF Request Parameters and REST URLsBy Roger Keays, 13 February 2013, 1:23 PM |
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 URLsPut 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.
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.
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¤cy=AUD and forget to add the hidden fields to retain them. It's also a weird mix of JSF and HTML.
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.
![]() |
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 surfing. You can follow Roger on Twitter and Google+. |
| « How To Export Google Reader Feeds Into Opera | Back to Blog | How To Get The HTTP Status Code In Selenium WebDriver » |