BMC to acquire Netreo. Read theBlog

Learn to Fully Leverage JavaServer Faces

By: Eugen
  |  March 17, 2023
Learn to Fully Leverage JavaServer Faces

JavaServer Faces as a Web Framework

The focus of this article is to introduce and explain the JavaServer Faces framework.

We’re going to start with a high-level look and them move on to the core details of JSF, on a practical example.

JavaServer Faces is not just a web component framework. It also provides the whole programming model of interaction between the browser and the server over HTTP.

The framework is capable of processing browser events on the server, keeping server model in sync with the client, rendering the page and a lot more interesting and useful functionality.

Understanding the lifecycle of a JavaServer Faces page is crucial if you want to fully take advantage of all possibilities of the framework. You also need to understand the provided points of extension for this lifecycle.

It’s worth noting that JSF tries to keep both the business logic and the user interface logic of the application – on the server. This allows you to write your code in Java without the need to care about the complexities of the frontend, including JavaScript and asynchronous interaction over HTTP.

Typical JSF Page Lifecycle

Now let’s see what a typical lifecycle of a web page looks like in JSF. It consists of multiple named phases – which you will see later during the course of the article.

Suppose an HTTP request came up from the browser with the information about some event. This could be clicking on a button, changing the value of a field, clicking a navigation link, submitting form etc.

Here is a simplified process for this request – consisting of 6 phases:

  • restore the view as it was rendered during the response to the previous request (“Restore View”)
  • populate the view with new values from the client, to get the picture of what the user was seeing when she triggered the event (“Apply Requests”)
  • validate the data from the UI to produce some helpful hints and errors for the user (“Process Validations”)
  • update the backing model beans in accordance with the view (“Update Model Values”)
  • invoke the actions of the business logic (“Invoke Application”)
  • render the new version of the page, or another page, if user’s actions led to navigational transitions (“Render Response”)

The first five phases are sometimes logically grouped into one “Execution” phase. The last phase is called the “Render” phase.

Model-View-Controller in JSF

MVC is a widely adopted pattern in the Java ecosystem. To get a deeper understanding of how this lifecycle relates to the structure of a JSF application, let’s take a look at how things work from the point of view of the standard MVC pattern.

First off, you should bear in mind that JSF keeps the structure of the web page on the server – as a tree of components or a view.

The framework populates this tree with some actual values (input text, selected/cleared state of a checkbox etc.). This is the exact structure that JSF restores at the first “Restore View” phase of request processing.

The model of a page in JSF is essentially a backing bean. This bean should know nothing about the structure of the web page. It should only keep track of the data relevant for the rendering of the view.

The framework is responsible for updating the model bean according to the changes on the page (“Update Model Values”). It’s also responsible for rendering the new version of the page later according to the changes in the model (“Render Response”).

You won’t find a special “controller” class in JavaServer Faces. In a typical MVC approach, there is always an open question on how you introduce abstraction and separate the UI logic from the business logic. And, clearly, cramming all of this logic into a single controller would become difficult to maintain and understand very quickly.

Action Listeners and Actions

JSF splits the controller tier into action listeners and actions; the difference between them is critical to understand.

Action Listeners

Action listeners are the handlers of the user interface events. They know about the structure of the UI, and they know about the web component that triggered them. The main purpose of action listeners is to change the backing model bean according to UI events.

Action listeners are invoked after the “Process Validations” phase, so they may rely on the UI data having been validated. Since the “Process Validations” phase may short-circuit the processing of a request in case of a validation error, the action listeners wouldn’t be invoked in such case.

It would not be ideal to place business logic or anything unrelated to the user interface – inside these listeners. For example, you normally shouldn’t try to persist data or send messages to JMS queues inside an action listener.

Actions

Actions are the handlers of the business logic of the application; they’re not aware of the UI structure or the components.

The purpose of the actions in a more general sense is to execute some business logic on the request from the user and provide the result that can be used by the framework to navigate the user to the next page.

Actions may return any kind of object, but it will be converted to a String object by the framework. This string is called an outcome, and it is used to select a view to navigate to. In the simplest case, an action will simply return the name of the view.

But this doesn’t mean that the actions have to be aware of the structure or the names of the views in your application. As discussed, actions should only be concerned with business logic.

So, to separate the business logic from the UI, you need to use the navigation rules that map the outcomes from specific views to corresponding target views. You can do this in the faces-context.xml:

<navigation-rule>
    <from-view-id>/register.xhtml</from-view-id>
    <navigation-case>
        <from-outcome>register-success</from-outcome>
        <to-view-id>/hello.xhtml</to-view-id>
    </navigation-case>
</navigation-rule>

Here, you map the register-success outcome from the register.xhtml view to navigate to the hello.xhtml view.

Actions are invoked during the “Invoke Application” phase. The action listeners were already invoked up to this point, and the model has been changed accordingly. So now you can run your business logic against the actual state of the model.

Separating Presentation and Business Logic

Let’s move on from the theory – and have a look at an example of using an action listener and an action in conjunction.

Suppose you need to provide the user with a registration form and show them a form asking for their first and last name. After the user clicks the registration button, you want to redirect them to a greeting page.

Preparing the View

As you need to dynamically change the login field as a result of changes to other fields – it’s a good idea to make the change submission with an AJAX request. This would allow you to re-render only specific parts of the page, without submitting and rendering it completely.

To achieve that, let’s first add the JavaScript part of the JSF framework to the header of the page:

<h:head>
    <h:outputScript library="javax.faces" name="jsf.js" />
</h:head>

Now, to notify the backend about the changes of the field value, you should add the valueChangeListener attribute. To send the request via AJAX, you can add a nested f:ajax tag:

<h:inputText id="name" value="#{userBean.name}"
  valueChangeListener="#{userBean.nameChanged}">
    <f:ajax event="change" execute="@this" render="proposed-login"/>
</h:inputText>

The event attribute of the f:ajax tag specifies which component event should be fired to the backend with an AJAX query.

The execute=”@this” attribute means that the backend should process only the changed UI component, without rebuilding the whole component tree.

And the render attribute specifies the identifier of the component that should be re-rendered as the result of the AJAX request. In your case, this is the id of the field containing the proposed login value.

The same should be done with the last name field.

As for the proposed login field, it should be simply bound to the corresponding user bean field:

<h:outputLabel id="login-label" value="Proposed Login:"/>
<h:inputText id="proposed-login" disabled="true" 
  value="#{userBean.proposedLogin}"/>

Notice that the change listeners and the data model are located in the userBean instance. But the handler for the submit button is located in the userControllerBean – to separate it from the user interface logic:

<h:commandButton value="Submit" action="#{userControllerBean.register}"/>

Model and Presentation Logic

Now let’s look at the UserBean model. This is a session-scoped bean with several fields and value change handlers:

import javax.faces.bean.*;
import javax.faces.event.ValueChangeEvent;

@ManagedBean
@SessionScoped
public class UserBean {

    private String name = "";

    private String lastName = "";

    private String proposedLogin = "";

    public void nameChanged(ValueChangeEvent event) {
        this.proposedLogin = event.getNewValue() + "-" + lastName;
    }

    public void lastNameChanged(ValueChangeEvent event) {
        this.proposedLogin = name + "-" + event.getNewValue();
    }
}

The handlers receive the ValueChangeEvent instance, and you can use it to get the new value of the field.

You can’t just use the name or lastName field, because, as you remember, the change listeners are invoked before the “Update Model Values” phase. So the model yet contains old values of the fields.

Controller and Business Logic

Now let’s look at the controller bean. In this example, it is actually quite simple, and it’s a good idea to make it request-scoped:

import javax.faces.bean.*;

@ManagedBean
@RequestScoped
public class UserControllerBean {

    public String register() {
        return "register-success";
    }
}

The register() method simply returns the outcome which is then mapped to the hello.xhtml view by the framework. The important thing to note is, there is no presentation-related code here.

In this controller bean, you can freely invoke any business logic you need, and by the end decide on the outcome of the operation. This outcome will be mapped by the framework directly to the view name. 

Phase Events

One of the powerful extension points of the JSF framework is the events. It allows you to execute some code before and after any of the six lifecycle phases.

Using PhaseListener

One of the ways to create an event listener is by implementing the javax.phases.event.PhaseListener interface and registering it with the framework by using one of the several options.

The PhaseListener interface consists of the following methods:

void afterPhase(PhaseEvent event);

void beforePhase(PhaseEvent event);

PhaseId getPhaseId();

You’re supposed to implement the getPhaseId() method to subscribe to a certain phase, and other methods to execute before and after the phase respectively.

If you look at the PhaseId class, you’ll see the list of six phases that you’re already familiar with:

PhaseId RESTORE_VIEW = new PhaseId("RESTORE_VIEW");
PhaseId APPLY_REQUEST_VALUES = new PhaseId("APPLY_REQUEST_VALUES");
PhaseId PROCESS_VALIDATIONS = new PhaseId("PROCESS_VALIDATIONS");
PhaseId UPDATE_MODEL_VALUES = new PhaseId("UPDATE_MODEL_VALUES");
PhaseId INVOKE_APPLICATION = new PhaseId("INVOKE_APPLICATION");
PhaseId RENDER_RESPONSE = new PhaseId("RENDER_RESPONSE");

And also the ANY_PHASE value which allows you to subscribe to any phase of the lifecycle:

PhaseId ANY_PHASE = new PhaseId("ANY");

Phase events are not bound to any particular component, but they can access FacesContext and do something with the current state of request processing, for example, modify the component tree.

Using Phase Events to Implement a Feature Switch

Now let’s put these phase events to good use. We’re going to simply test a new feature and render it – only for the users with a certain IP value or IP range. Of course, we should be able to remove or change this limitation later, if we need to.

Let’s consider the following input form. The ability to input user’s last name is the new feature that you wish to hide from everyone except for specific IP addresses:

<h:form>
    <h:panelGrid columns="2">
        <h:outputLabel value="First Name:"/>
        <h:inputText id="name" value="#{userBean.name}"/>
        <h:outputLabel id="new-feature-last-name-label" value="Last Name:"/>
        <h:inputText id="new-feature-last-name" value="#{userBean.last-name}"/>
        <h:commandButton value="Submit" action="#{userBean.greet}"/>
    </h:panelGrid>
</h:form>

Notice that the identifiers of the components that are relevant for the new feature start with “new-feature-“. The backing userBean could be as simple as:

import javax.faces.bean.*;

@ManagedBean
@SessionScoped
public class UserBean {

    private String name;

    private String lastName;

}

And to separate the business logic, as in the previous example – you’ll create a special controller bean for the navigation:

import javax.faces.bean.*;

@ManagedBean
@RequestScoped
public class GreetControllerBean {

    public String greet() {
        return "greet";
    }
}

You could take advantage of the PhaseListener interface, implement it as a listener of the RENDER_RESPONSE phase, and register it in the faces-config.xml file:

<lifecycle>
    <phase-listener>com.stackify.deepjsf.FeatureEnableListener</phase-listener>
</lifecycle>

Unfortunately, when opening the page for the first time, this listener won’t be able to traverse the component tree. This is because the tree is only built during the RENDER_RESPONSE phase. You’ll see how to work around this limitation in the following section.

Using the View-Specific Phase Listener

You can address the earlier limitation by using the view-specific phase listener. Let’s wrap the form in the view tag with beforePhase attribute specified:

<f:view beforePhase="#{phaseListenerBean.beforeListener}">
    <h:form>
    <!-- ... -->
    </h:form>
</f:view>

Here, you don’t refer to a bean, but to a specific method.

Another difference, when compared to the global listener, is that this view-specific listener will always be invoked before every phase for this view. And so it’s important to check the phase is correct here.

Let’s have a look at the implementation:

import javax.faces.bean.*;
import javax.faces.component.*;
import javax.faces.event.*;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

@ManagedBean
@RequestScoped
public class PhaseListenerBean {

    public void beforeListener(PhaseEvent event) {
        if (!event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
            return;
        }
        UIViewRoot root = event.getFacesContext().getViewRoot();

        boolean showNewFeature = showNewFeatureForIp(event);

        processComponentTree(root, showNewFeature);
    }
}

For the IP check, we’re using the FacesContext and get access to the raw HttpServletRequest. If a proxy is involved, a little more work will be required:

private boolean showNewFeatureForIp(PhaseEvent event) {
    HttpServletRequest request = (HttpServletRequest) event.getFacesContext()
      .getExternalContext().getRequest();
    String ip = request.getRemoteAddr();
    return !ip.startsWith("127.0");
}

One way to walk the view tree is using some simple recursive logic. Alternatively, a simpler way is to make use of a standard feature of JSF — the UIComponent.visitTree() method:

private void processComponentTree(UIComponent component, 
  PhaseEvent event, boolean show) {
    component.visitTree(VisitContext.createVisitContext(
      event.getFacesContext()), (context, target) -> {
        if (
          target.getId() != null
          && target.getId().startsWith("new-feature-")
          && !show) {
            target.setRendered(false);
        }
        return VisitResult.ACCEPT;
      });
}

Now you can check that the last name field is visible only if the user’s IP corresponds to the predefined value.

Conclusion

In this article, you’ve familiarized yourself with the basics of JSF and then had a look at some of the core functionality more in-depth.

As always, the code backing the article is available over on GitHub.

With APM, server health metrics, and error log integration, improve your application performance with Stackify Retrace.  Try your free two week trial today

Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

Learn More

Want to contribute to the Stackify blog?

If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]