5

I've been using JSF for many years and in the next project we're aiming to make the web-tier as stateless as possible. One possibility I'm exploring is removing @ViewScoped beans in favour of @RequestScoped (plus one or two @SessionScoped beans as required). This is proving troublesome for complex pages with AJAX, datatables and conditional rendering. My question is: how well does JSF (and PrimeFaces) work with stateless web beans? Is this something that I should continue to explore, or is @ViewScope now so fundamental that it's not worth the effort?

I appreciate as I write this question that it might be be closed as 'primarily opinion based', however I'm hoping that it isn't, I'm interested in specific problems that @ViewScope solved and what historic workarounds I'd have to re-introduce by ignoring @ViewScoped.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
StuPointerException
  • 7,117
  • 5
  • 29
  • 54
  • 1
    Sounds like old jsf 1 times, where we stored data in hidden fields to restore state on the server (including checks for valid/manipulated hidden fields) – TomStroemer May 05 '20 at 20:05
  • 1
    _"In the next project we're aiming to make the web-tier as stateless as possible."_ Care to motivate? I'm always curious... I tried it once in a project and it did not outweigh the small advantages. Certainly not in a well setup server environment. To the point: Plain jsf should not have issues at all and for PrimeFaces components it should work too and if a component in a certain case does have an issue, they are quick to fix these (complex combinations of datatable features might be exempt though) – Kukeltje May 05 '20 at 20:20
  • 1
    My two cents is this is pretty tough to do especially if using Datatables. – Melloware May 05 '20 at 22:29
  • 2
    Conditional rendering is "easy" to do with hidden input fields and/or request parameters (though this way they are much more easily tamperable by hackers, so you'd better not use them on sensitive things :) ). Datatables is indeed tricky, the [OptimusFaces](https://github.com/omnifaces/optimusfaces) library transparently makes the sorting/filtering/paging functionality of `` fully idempotent by automatically updating the request query string. This allows you to back it with a request scoped bean. I'll perhaps write this weekend a more elaborate answer on this all. – BalusC May 05 '20 at 22:34
  • @Kukeltje Thanks for the comment. We've always focused on making the web-tier as stateless as possible by using the narrowest scope feasible for a given component. However, we've recently found that `@RequestScope` is now almost never used, the go-to scope is always `@ViewScope`. This has led to business logic seeping into the web-tier from the service-tier (which are often micro-services) based on convenience. Given that we want people to be able to create clients that are not solely JSF clients, the idea of limiting `@ViewScope` was raised. – StuPointerException May 06 '20 at 19:23
  • @BalusC thanks for your comment, information you posted 10 years ago when `@ViewScope` became part of the specification was the very reason I started to doubt my approach and ask this question! – StuPointerException May 06 '20 at 19:44
  • Instead of limiting view-scoped, why not move business logic back into the service tier where it belongs? Works for us... – Kukeltje May 07 '20 at 06:46

1 Answers1

4

How well does JSF (and PrimeFaces) work with stateless web beans?

It's technically possible.

JSF uses the view state primarily to keep track of the "disabled", "readonly" and "rendered" attributes of the UIInput and UICommand components as well as the "submitted value", "local value" and "is valid?" states of the EditableValueHolder components (implemented by among others UIInput).

In case of "disabled", "readonly" and "rendered" attributes, if these represent an EL expression, then JSF will re-check it during processing the form submit request. Below is a basic example:

<h:form>
    <h:commandButton value="toggle" action="#{bean.toggle}">
        <f:ajax render="panel" />
    </h:commandButton>
    <h:panelGroup id="panel">
        <h:commandButton value="submit" action="#{bean.submit}" rendered="#{bean.toggled}">
            <f:ajax />
        </h:commandButton>
    </h:panelGroup>
</h:form>
@Named
@ViewScoped
public class Bean implements Serializable {

    private static final long serialVersionUID = 1L;

    private boolean toggled;

    public void toggle() {
        this.toggled = !toggled;
    }

    public void submit() {
        System.out.println("Submitted");
    }

    public boolean isToggled() {
        return toggled;
    }
}

First click the "toggle" button and then the "submit" button. In case of a view scoped bean, it'll work just fine. If you however replace @ViewScoped by @RequestScoped here, then it'll fail, because toggled defaults back to false at the moment JSF needs to decode the "submit" button during the postback request, and so its rendered attribute will evaluate false and ultimately JSF won't queue the action event.

In such case, you need to make sure yourself that the property is preinitialized to the expected value during (post)construction of the request scoped bean. One way is to use hidden input fields for this within the ajax-updated component. Here's the adjusted example:

<h:form>
    <h:commandButton value="toggle" action="#{bean.toggle}">
        <f:ajax render="panel" />
    </h:commandButton>
    <h:panelGroup id="panel">
        <input type="hidden" name="toggled" value="#{bean.toggled}" />
        <h:commandButton value="submit" action="#{bean.submit}" rendered="#{bean.toggled}">
            <f:ajax />
        </h:commandButton>
    </h:panelGroup>
</h:form>
@Named
@RequestScoped
public class Bean {

    @Inject @ManagedProperty("#{param.toggled}")
    private boolean toggled;

    public void toggle() {
        this.toggled = !toggled;
    }

    public void submit() {
        System.out.println("Submitted");
    }

    public boolean isToggled() {
        return toggled;
    }
}

NOTE: a <h:inputHidden> will unfortunately not work as it updates the model value only after the action event is to be queued. Even not with a immediate="true" on it. This brings me by the way on the idea for a new <o:inputHidden> for OmniFaces.

With these changes, it'll work fine.

However, as the state which was originally view scoped (the toggled property) has now become a request parameter, it's fully exposed to the world and therefore also tamperable by hackers. Hackers wanting to invoke the "submit" button without invoking the "toggle" button first can now simply manually add a request parameter toggled=true. Whether that's desirable depends on the business requirements of your application, but more than often it's totally undesirable.

This is what JSF is trying to protect you from by offering the possibility to put these sensitive properties in a @ViewScoped bean instead.


This is proving troublesome for complex pages with AJAX, datatables and conditional rendering

True, but still not technically impossible. You only have to manually carry around the paginated, sorted and filtered states via manually populated hidden input fields as demonstrated above. The <p:dataTable> supports binding these states to bean properties. For example:

<p:dataTable ...
    first="#{bean.first}"
    sortField="#{bean.sortField}"
    sortOrder="#{bean.sortOrder}"
    filterBy="#{bean.filterBy}">
    ...
</p:dataTable> 

You can just copy them into <input type="hidden"> fields as demonstrated before (which you make sure are covered by <p:ajax update>!) and finally grab them via @ManagedProperty and/or the @PostConstruct.

In effects, you're this way basically reinventing the job currently already done by javax.faces.ViewState hidden input field in combination with @ViewScoped beans. So why not using it right away? :)

If your primary concern is the memory usage, then you need to carefully design your beans in such way that only the view scoped state is stored in a @ViewScoped bean and that only the request scoped state is stored in a @RequestScoped bean. For instance, it's perfectly fine to put the data model in the request scoped bean and the paginated/sorted/filtered state in the view scoped bean. You may also want to consider OmniFaces @ViewScoped instead as that immediately destroys the view state and the physical bean when the page is unloaded.

That said, with this question in mind, I've just a few hours ago verified and improved the OptimusFaces library to ensure that it also fully supports stateless views with <f:view transient="true">, along with a new integration test. The advantage of OptimusFaces is among others that you don't anymore need to manually worry about carrying around the paginated/sorted/filtered state. OptimusFaces will worry about it for you.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555