Parametrized View State

Giving JSF some REST

Why?

There are complaints that JSF does not handle GET requests well. This is however only one aspect of a deeper problem: The view state does not depend on  the GET parameters. Imagine you have a product details page where the user can switch the image size. It makes sense if this thereafter applies to all product details pages. But in many cases this is not the desired behaviour. If you have some details editor page it is shurely not desired behaviour that form data may leak between different pages. So the definition of which view states are parametrized is application dependent.

How?

View state is identified internally by the view identifier (a.k.a. viewId).This is actually the URL of of the underlying JSP. According to the spec (see 7.5.2) this JSP is accessed via ExternalContext.dispatch. That is the place where we can get the hook in. We want mulötiple view states that use the same JSP. The trick is to put the parameters left of ".jsp" and then to rewrite the viewId to remove these parameters when dispatch is called. As a result each page that is accessed by something like edit_item.key-246813634.jsf has its own view state, while they all use edit_item.jsp.

This can be done with the Url Rewrite Filter (based on the very useful mod_rewrite for apache) which can rewrite forwards. To do this web.xml needs the following filter at the start of the filter chain.

<filter>
<filter-name>UrlRewriteForwardFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<!-- Recommemnded while starting -->
<param-name>logLevel</param-name>
<param-value>DEBUG</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>UrlRewriteForwardFilter</filter-name>
<url-pattern>/*</url-pattern>
<!-- only with web-app_2_4.xsd -->
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
This filter is configured (in urlrewrite.xml) as follows (an example):
<rule>
<from>edit_item\.key-([0-9]+).jsp</from>
<to>edit_item.jsp?key=$1</to>
</rule>

What else?

Thats it? Not yet. You will notice that the above makes navigation rules a problem. This can be dealt with
<from-view-id>/pages/edit_item.*</from-view-id>

The other thing that is needed is an accessor bean for the view state parameters that behaves like the builtin param. The problem with directly using param is, that it has request scope. You will need to access the view state parameters when you leave the page (via postback). Param will be gone by then. This is sketched below: Note that the code below has not been seriously tested yet.

/**
* The purpose of ViewStateParam is to make the parameters
* in the view id accessible in a way similiar to request parameters.
* This class implements a read only map that can be instantiated
* as a none scope bean to achieve that.
*/
public class ViewStateParam implements Map<Object, Object>{

//...


/**
* Retrieves the view id for the current instance.
* @return the view id
*/
String getId() {
FacesContext c = FacesContext.getCurrentInstance();
UIViewRoot v = c.getViewRoot();
return v.getViewId();
}

/**
* This method is necessary primarily because
* decoding has to be done _before_ any comparision.
* @return All decoded name/value pairs
*/
Entry[] parseParams()
{
String id = getId();
String pairs[] = id.split("\\.");
Entry rtn[] = new Entry[pairs.length-2];
for (int i=1; i<pairs.length-1;i++) {
String pair[] = pairs[i].split("-");
rtn[i-1] = new Entry(decode(pair[0]), decode(pair[1]));
}
return rtn;
}

/**
* Get a ViewState parameter by name. This assumes that
* there is only one Parameter for a given name.
* @param name
* @return The parameter value
*/
public Object get(Object aKey)
{
String name = (String)aKey;
for (Entry entry: parseParams()) {
if (entry.getKey().equals(name)) {
return entry.getValue();
}
}
return null;
}

//...

The bean above should be used with scope none, while it has de facto view state scope.

Thats it. In case of questions or comments feel free to contact me.