May 17, 2015

RESTful JSF with POST-redirect-GET (part 4 of 5)


Pages: 1 2 3 4 5

Going @RequestScoped

So far, we’ve built our application with @ViewScoped / @SessionScoped controllers. But wait, you say, as page navigation is stateless, can’t we go for stateless controllers as well... @RequestScoped controllers?

Yes, we can, and we will, although we’ll have to overcome some technical difficulties.

Concept

For your own RESTful web application, you’ll have to decide whether to go for @RequestScoped controllers or not by comparing the advantages:
  • Server-side memory consumption minimized: there is just no state.
  • “Open session in view” problem is prevented.
  • Memory leaks through session pollution are prevented: there are just no non-empty sessions.
  • Session timeouts are prevented: ditto.
to the disadvantages:
  • Data access rate maximized: On every request, the data of the current view needs to be fetched from the backend.
  • You cannot use AJAX. (We’ll discuss later how to address this issue.)
Thus in an application where the bottleneck is memory consumption on the (view layer) JVM, and not data retrieval rate at the backend, I would suggest opting for @RequestScoped controllers.

Implementation

In order to change to @RequestScoped controllers, we have to adjust some parts of the application, and watch out for some pitfalls.

GET

For GET, nothing changes. Everything still works, with or without parameters.

POST-redirect-GET: Reload on PreValidate

There is however a problem with POSTing a form with applies e.g. to the Save action on /customers/edit.xhtml.

With a @RequestScoped controller, the current implementation would immediately throw an exception:
javax.faces.FacesException: /pages/customers/edit.xhtml @48,123 
    value="#{customerController.currentEntity.name}": 
    Target Unreachable, 'null' returned null
Note that the error does not happen in a <f:viewAction>. Actually, its execution is never reached as the error happens in the PROCESS_VALIDATIONS phase in the first lifecycle run already:

As the backing bean is request scoped, it is rebuilt from scratch with the “Save” request. As the action does not set immediate="true", the full life cycle is invoked including phase 3, PROCESS_VALIDATIONS. However, as the backing bean has just been recreated, customerController.currentEntity is now null.

To prevent the exception, we must initialize customerController.currentEntity prior to validation. We do so by refetching the entity from the backend. Why not just create a new entity e.g. with the default constructor? After all, the entity’s values will be overridden during UPDATE_MODEL_VALUES anyways. Yes, but due to lazy loading, only those values would be set which actually have a corresponding input component in the form. We cannot rely on that and thus we have to refetch the entire thing.

Technically, we could use @PostConstruct, but as I mentioned previously, I don’t like mixing up concepts.

Instead, we use an additional <f:event>:
<f:metadata>
    <f:viewParam name="id" value="#{customerController.currentEntityId}"/>
    <f:event type="preValidate" listener="#{customerController.initCurrentEntity}"/>
    <f:viewAction action="#{customerController.initCurrentEntity}"/>
</f:metadata>
The event reuses the method bound to the viewAction which now includes a bit more logic:
public String initCurrentEntity() {
    if (currentEntityLoadedOutcome != null) {
        String ret = currentEntityLoadedOutcome;
        currentEntityLoadedOutcome = null;
        return ret;
    }
    
    // load param during preValidate phase (if @RequestScoped)
    String idParam = Faces.getRequestParameterMap().get("id");
    if (!Strings.isEmpty(idParam)) {
        currentEntityId = Long.parseLong(idParam);
    }
    
    // without id param: CREATE
    if (currentEntityId == null) {
        currentEntity = createNewEntity();
    }
    else {
        // with id param: READ
        currentEntity = getService().findById(currentEntityId);
        if (currentEntity == null) {
            Messages.addGlobalError("Entity with id " + currentEntityId + " not found!");
            currentEntityLoadedOutcome = "list.xhtml";
            return currentEntityLoadedOutcome;
        }
    }
    currentEntityLoadedOutcome = "edit.xhtml";
    return "edit.xhtml";
}
  • If the method is triggered by <f:event>, we must retrieve the id parameter value directly from the request parameter map.
  • As a GET request would now trigger both the <f:event> and the <f:viewAction>, we want to make sure that it only runs through once for performance reasons.
Why not just kicking out <f:viewAction>? It’s because <f:viewAction> has the ability to redirect (e.g. on error) through the String return value of the method. Yes, you could achieve the same with some hacks in <f:event>, but I don’t like that. With the current design, one could set the controller back to @ViewScoped, delete the <f:event> tag, and everything would still be best practices.

Bugfix: @RequestScoped commandButton in dataTable

Unrelated to RESTful navigation, there is a problem with <commandButton> / <commandLink> when rendered inside a <column> of a <dataTable> if their action is backed by a @RequestScoped backing bean: this button’s action will not be invoked. This is the case e.g. for the Delete button (“X”) in the Delete column in customers/list.xhtml’s dataTable.

This behavior is explained in this stackoverflow answer. Apparently, JSF cannot invoke a button’s method in this situation. Of course, the suggestion to switch to a @ViewScoped bean is not acceptable here.

There is, however, another workaround which is here realized using some OmniFaces functionality:
    <h:column>
        <f:facet name="header">
            <h:outputText value="Delete"/>
        </f:facet>
        <a href="#" onclick="deleteAction({ deleteId: '#{item.id}'})">X</a>
    </h:column>
</h:dataTable>
<o:commandScript name="deleteAction" action="#{customerController.delete}" immediate="true"
                 oncomplete="window.location='list.xhtml';"/>

  • <o:commandScript> registers a JavaScript function of the given name with triggers an AJAX request to the given backing bean action.
  • This function is triggered by an <a> element’s onclick attribute (<o:commandScript> supports providing HTTP request parameters to the backing bean action). Because <a> must define a href attribute, we set that to "#" to prevent navigation.
  • The <o:commandScript> also registers an AJAX callback (oncomplete) which here reloads the page. Note that we must explicitly redirect, not just reload, in order to get rid of that "#" again.
Here’s the implementation of the backing bean method:
public String delete() {
    Long id = Long.parseLong(Faces.getRequestParameter("deleteId"));
    return delete(id);
}
This parses the deleteId param from the HTTP request parameter map and then invokes the actual business method.

Warning: If the dataTable is part of a <o:form includeRequestParams="true"> you must not use a parameter name which clashes with one of the <f:viewParam>s; otherwise, on form submit, the parameter value explicitly set in the JavaScript method call would be overridden by the <f:viewParam>’s value.

Because in the example application, we want to reuse the same logic for /customers/edit.xhtml’s payments dataTable where a <f:viewParam name="id"> is already present, I chose the more explicit deleteId parameter name.

Bugfix: @RequestScoped commandButton with conditional rendered attribute

Unrelated to RESTful navigation, there is another problem with <commandButton> / <commandLink> with a conditional rendered or disabled attribute if their action is backed by a @RequestScoped backing bean: this button’s action will simply not be invoked. This is the case e.g. for the Delete button in customers/edit.xhtml.

The causes are explained in this stackoverflow answer and here as well. Apparently, JSF’s design prevents proper action invocation in this situation for any conditionally rendered / disabled <commandButton> / <commandLink>.

As a workaround, I created a phase listener which sets the rendered attribute on all components “hard-coded” to true right before APPLY_REQUEST_VALUES phase and sets it back to the original value after APPLY_REQUEST_VALUES phase.

Note that this knocks out that “invocation safety” check which is hard-wired in JSF. On the other hand, I think that in order to really prevent the user from pressing a button, you should anyways do more than just making that button invisible. In this application for example, we set the Delete button to invisible only because it would not make sense to delete a not-yet-persisted entity; it would just trigger an Exception.

Anyway, I you want to apply this solution, you have to register the phase listener within <f:view>:
<f:view beforePhase="#{forceRenderActionSourceListener.forceRenderActionSource}"
    afterPhase="#{forceRenderActionSourceListener.forceRenderActionSourceReset}">
The listener must be a bean with a scope greater than request scope. Due to its stateless nature, I’ve used @ApplicationScoped here.

Please refer to the application’s source code for the complete implementation of ForceRenderActionSourceListener.

Bugfix: commandButton with immediate="true" attribute based on <f:event> values

Unrelated to RESTful navigation, there is yet another problem with <commandButton> / <commandLink> with the immediate="true" attribute if their action relies on a backing bean value being set / initialized within an <f:event> invocation: as the event would be fired too late in the JSF lifecycle, the value is not set yet, potentially leading to Exceptions. This is not a direct consequence of, but is likely always the case if the backing bean is @RequestScoped. This is the case e.g. for the Delete button in customers/edit.xhtml, which relies on the execution of BaseController#initCurrentEntity().

Setting immediate="true" on a <commandButton> is the default solution to skip the validation / model update phases of the JSF lifecycle.

Now observe this implementation of the Delete button:
<h:commandButton value="Delete" action="#{customerController.delete(customerController.currentEntity.id)}" 
                                 immediate="true" rendered="#{not empty customerController.currentEntity.id}"/>
Remember that we established in the POST-redirect-GET: Reload on PreValidate section, that customerController.currentEntity will be null during PROCESS_VALIDATIONS (3) phase; that’s why we re-initialized the controller with
<f:event type="preValidate" listener="#{customerController.initCurrentEntity}"/>
The problem is now that immediate="true" skips any phase after APPLY_REQUEST_VALUES (2); the preValidate event listener is thus never called, and customerController.currentEntity.id stays null.

The workaround is that instead of sending the parameter value in question as a EL method parameter, to set it with <f:param>.
<h:commandButton value="Delete" action="#{customerController.delete}" immediate="true"
                 rendered="#{not empty customerController.currentEntity.id}">
    <f:param name="deleteId" value="#{customerController.currentEntity.id}"/>
</h:commandButton>
In the backing bean method, we then have to retrieve the parameter value from the request parameter map:
public String delete() {
    Long id = Long.parseLong(Faces.getRequestParameter("deleteId"));
    return delete(id);
}
Yes, we re-use the server-side logic established in the Bugfix: @RequestScoped commandButton in dataTable section.

AJAX

As mentioned earlier, a major disadvantage of having a @RequestScoped backing bean is that you cannot use AJAX calls! An AJAX call would be handled as a new request, thus destroying and recreating the backing bean. This is harmful because the AJAX request will by definition only carry a fraction of the information a full page reload would request / receive. Thus state would get lost, triggering NullpointerExceptions and other undesired behavior.

My advice is to not try to cheat the system. Either embrace the statelessness of your controller or switch to a stateful implementation.

On the other hand, you can of course include dynamic behavior in your request scoped pages: by using JavaScript. If you think about it, this actually makes sense. With the pure stateless nature of the HTTP protocol, the only way to keep state is to keep it on the client. This is exactly how JavaScript based frameworks work. It may even be the superior solution in some cases: Rather than cluttering the server side with logic and state which belongs to the client, keep it local.

Our application carries an example of this. Do you remember this requirement we have for the customer edit view: “A customer of employment type unemployed must not have a company name.” In plain JSF, one might be tempted to implement this with a conditional rendered attribute which is changed by the dropdown’s onchange AJAX action:
<h:selectOneMenu id="employmentStatus" value="#{customerController.currentEntity.employmentStatus}" 
                 title="Employment status"
                 converter="omnifaces.GenericEnumConverter">
    <f:selectItems value="#{EmploymentStatus:values()}"/>
    <f:ajax event="change" render="@form:companyNameLabel @form:companyName"/>
</h:selectOneMenu>
<h:outputLabel id="companyNameLabel" value="Company name:" for="companyName"
               rendered="#{customerController.currentEntity.employmentStatus != 'Unemployed'}"/>
<h:inputText id="companyName" value="#{customerController.currentEntity.companyName}" title="Company name:" 
             rendered="#{customerController.currentEntity.employmentStatus != 'Unemployed'}"/>
But with a @RequestScoped backing bean, this will just not work. We have to come up with a JavaScript based solution. Of course, you would basically use any JavaScript framework however sophisticated you’d like it to be. For this example, I used a plain jQuery-based solution:
<o:onloadScript>CustomerEdit.companyNameInit('form:employmentStatus');</o:onloadScript>
…
<h:selectOneMenu id="employmentStatus" value="#{customerController.currentEntity.employmentStatus}" 
                 title="Employment status"
                 converter="omnifaces.GenericEnumConverter"
                 onchange="CustomerEdit.companyNameInit(this.id);">
    <f:selectItems value="#{EmploymentStatus:values()}"/>
</h:selectOneMenu>
where CustomerEdit.companyNameInit(employmentStatusComponentId) triggers hiding / unhiding and wiping of the companyNameLabel and companyName component, based on the current employment status.

Please refer to the complete source code if you’re interested in the JavaScript implementation.

I’d assert that from a software design as well as from a bandwith consumption point of view, this implementation is even superior to a server-side solution: If the problem is local, then solve it locally.

However, remember that no client side solution is save! You have to assume that the user is able to disable it. Thus you have to provide a server-side safety net. For this example, I thus created a validation constraint which prevents the user from submitting the form if that restriction is violated:
<o:validateMultiple components="employmentStatus companyName" 
                    validator="#{customerController.isCompanyEmptyWhenUnemployed}"
                    message="{0}: If unemployed, no company name must be set."/>
Again, please refer to the source code for the server-side implementation of the validation method.

Pages: 1 2 3 4 5