Stateless vs Stateful JSF view parameters

JSF defines the concept of view parameters, which are used to support URL parameters in GET requests (although it can be used for non-faces POST parameters as well).

View parameters are defined on a Facelet with the help of the f:viewParam tag. A typical usage looks like this:

<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:h="http://java.sun.com/jsf/html"
     xmlns:f="http://java.sun.com/jsf/core"
 >
    <f:metadata>
        <f:viewParam name="foo_id" value="#{myBean.fooId}" />
    </f:metadata>

    <h:body>    
        #{myBean.fooId}
    </h:body>
</html>

In this case myBean could look like this:

@Named
@ViewScoped
public class MyBean {
    private Long fooId;

    public Long getFooId() {
        return fooId;
    }

    public void setFooId(Long fooId) {
        this.fooId = fooId;
    }
}

There's nothing really fancy going on here. When a GET request like localhost:8080/mypage?fooId=1 is processed, setFooId() is called, and when the page is rendered getFooId() is called. When we fire a GET request without the URL parameter, the setter will not be called. So, it looks like the presence of the URL parameter is what triggers the setter.

But what happens after we do a postback from that page? E.g. when we add the following to the Facelet:

<h:form>
    <h:commandButton value="do action" action="#{myBean.doAction"}"/>
</h:form>

and the following to the bean:

public void doAction() {
}

There will be no URL parameter present in the request after the postback, so it would seem logical the setter will not be called.

As it appears however, the setter will be called. What happens is that f:viewParam (UIViewParameter) is a stateful component. When it initially retrieves the URL parameter foo_id, it internally stores this in its view state. After the postback, the value is processed again and then also pushed into the model again (the backing bean). This is a direct consequence of the fact that in JSF a view is normally stateful and this behavior is actually consistent with how other UIInput components (like e.g. h:inputText) work. In case a backing bean is request scoped, this can also be really convenient as otherwise the initial URL parameter would be 'lost' after the first postback. See e.g. JSF 2.0: View parameters, where the author seems really happy with this behavior:

The UIViewParameter component also stores its value in the state, so that the value will also be available on the next postback request (most likely a clicked button or link). So you only need to provide your view parameter(s) once and not on every request.

Unfortunately, there are a couple of situations where a stateful f:viewParam can be a real nuisance. In the example given above the backing bean is already view scoped and takes care of managing its own state. Calling the setter again is simply redundant, but doesn't hurt much. Even if the backing bean loads data from a DB based on this parameter, it probably does so in the preRenderView event handler with a guard that checks if the request is not a postback:

public void onPreRenderView() {
    if (!FacesContext.getCurrentInstance().isPostback()) {
        foo = fooDao.getByID(fooId);
    }
}

However, things get a little bit problematic when taking advantage of one of the killer features of view parameters: the ability to attach converters and validators to them. Instead of letting the backing bean load the Foo instance, we can use a universal converter for this and while at it validate right away that the foo_id is legal to be used by the current user (thus preventing parameter twiddling attacks). The code would then look like this:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
 >
    <f:metadata>
        <f:viewParam name="foo_id" label="foo_id" value="#{myBean.foo}" converter="fooConverter" validator="fooOwnerValidator"  />
    </f:metadata>

    <h:body>    
        #{myBean.foo.id}

        <h:form>
            <h:commandButton value="do action" action="#{myBean.doAction"}"/>
        </h:form>
    </h:body>
</html>

and

@ViewScoped
@ManagedBean
public class MyBean {
    private Foo foo;

    public Foo getFoo() {
        return foo;
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    public void doAction() {
    }
}

With f:viewParam being stateful, it will now do the conversion and validation on each and every postback. This is not only unnecessary (as the correct Foo instance is already safely kept in the view scoped MyBean), but it's also a needless drain on system resources and a potential slow down for this view as it may involve a DB query for each and every postback. So what we need for this particular usecase is an f:viewParam variant that doesn't do this needless converting every time. There are a few options:

  1. Create a stateless version
  2. Only do conversion and model updates when value changes (suggested by my co-worker Bauke Scholtz)
  3. Also put converted value in state, always update model, reconvert when original changes

For this article I looked at the first option, as it seemed to be the easiest one to implement. The original UIViewParameter handles its state via the following methods (Mojarra 2.1.1):

@Override
public String getSubmittedValue() {
    return (String) getStateHelper().get(PropertyKeys.submittedValue);
}
public void setSubmittedValue(Object submittedValue) {
    getStateHelper().put(PropertyKeys.submittedValue, submittedValue);
}

In order to make a stateless variant, I simply inherited and provided alternative implementations for these two methods:

@FacesComponent("com.my.UIStatelessViewParameter")
public class UIStatelessViewParameter extends UIViewParameter {

    private String submittedValue;
 
    @Override
    public void setSubmittedValue(Object submittedValue) { 
        this.submittedValue = (String)submittedValue;
    }
 
    @Override
    public String getSubmittedValue() { 
        return submittedValue;
    } 
}

I then created a new tag for this component in a facelet-taglib file (e.g. my-taglib.xml in META-INF in a jar):

<namespace>http://my.com/test</namespace>
<tag>
    <tag-name>viewParam</tag-name>
    <component>
        <component-type>com.my.UIStatelessViewParameter</component-type>
    </component>
</tag>

After this I can use this stateless variant on my Facelets:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:my="http://my.com/test"
 >
    <f:metadata>
        <my:viewParam name="foo_id" label="foo_id" value="#{myBean.foo}" converter="fooConverter" validator="fooOwnerValidator"  />
    </f:metadata>

    <h:body>    
        #{myBean.foo.id}

        <h:form>
            <h:commandButton value="do action" action="#{myBean.doAction"}"/>
        </h:form>
    </h:body>
</html>

I can now do as many postbacks as I want, but the (expensive) conversion only happens once when the URL parameter is actually in the request. Exactly as we wanted!

There are two drawbacks though. The first is the fact that automatically generating a bookmarkable link that includes the original URL parameters via the includeViewParams attribute of e.g. h:link does not work anymore. The second is that if the required attribute has been set to true, then this will now complain the value is missing on a postback. This can be resolved by adding a #{!facesContext.postback} expression to this attribute, but this is not a very elegant solution.

To solve these drawbacks I'll have to look into the method suggested by my co-worker Bauke and this might be the topic of a followup article.

Update:

A simple solution to avoid having to add the #{!facesContext.postback} at every place is to add the following method to the UIStatelessViewParameter class given above.

@Override
public boolean isRequired() {
    // The request parameter get lost on postbacks, however it's already present in the view scoped bean.
    // So we can safely skip the required validation on postbacks.
    return !FacesContext.getCurrentInstance().isPostback() && super.isRequired();
}

Thanks goes to Bauke for coming up with this ;)

Arjan Tijms

Comments

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

Jakarta EE Survey 2022

Counting the rows returned from a JPA query