How to stay away from the JSF API

A few weeks ago, Ed Burns posted a link to a blog on the JSF expert group mailing list, commenting “A nice one, but it doesn't mention JSF 2”. Ever the curmudgeon, I pointed out that it wasn't so nice that the blog's sample code used the JSF API in beans when it wasn't necessary—as does in fact a lot of sample code, even in the official Sun tutorials. Ed's response: “Cay, a blog comment by such an eminent citizen as yourself would certainly be noticed.” So, here is the curmudgeonly eminence's advice on how to stay away from the JSF API.

To set the stage, recall the basic mechanism by which JSF links the visual presentation with the application logic. JSF pages are composed of component tags that contain expressions in the oh-so-blandly named Expression Language (EL). For example,

<h:inputSecret value="{userBean.password}"/>

The class of the userBean object must have getters and setters for the password property:

@Named public class UserBean {
   public String getPassword() { ... }
   public void setPassword(String newValue) { ... }
   ...
}

Here, the property has the type String, and that is good. There is no coupling between the UserBean class and the JSF API. You can compile and run unit tests of UserBean without having JSF around. You can even (gasp) switch to another view technology.

So, where do programmers go wrong? Mainly in these four areas:

  1. SelectItem
  2. Data tables
  3. UIComponent
  4. The @ManagedBean annotation

SelectItem

When you show a list of choices, by using radio buttons, checkboxes, listboxes, or dropdown menus, you need to provide a list of (label, value) pairs. Since Java lacks a pair type, the JSF API provides a class SelectItem that lets you specify an item's label, value, and a few other properties that aren't so useful. Your bean provides a read-only property of type Collection<SelectItem> for the JSF component:

<h:selectOneRadio value="#{orderBean.topping}>
   <f:selectItems value="#{orderBean.toppingItems}"/>
</h:selectOneRadio>

@Named public class OrderBean {
   Collection<SelectItem> getToppingItems() { ... }
   ...
}

But to compile this OrderBean, you need the JSF API.

This coupling is easy to avoid. You can use a Map<String, Object> containing the labels and values. (Use a TreeMap if you want the labels in alphabetical order, a LinkedHashMap if you need some other order.)

With JSF 2.0, you can do even better. Expose a Collection<ToppingItem> and use a tag such as the following:

<f:selectItems value="#{orderBean.toppingItems}" 
   var="topping"
   itemLabel="#{topping.name}" 
   itemValue="#{topping.id}" />

You design the ToppingItem class in any way you like. The sample above assumes that the class has getName and getId methods.

Data Tables

You can bind a JSF data table to a Collection<RowData>, where RowData is any type of your choice, or you can use the DataModel class in the JSF API. Why do people use a DataModel when it would be so much easier to just use a standard collection? There seems to be one compelling reason—locating the currently selected row in an action.

But you don't need the DataModel class for that. In JSF 1.2, you can use sPAL, which is admittedly a bit of a pain. JSF 2.0 is much better. Simply pass the row as a parameter to your action method.

<h:dataTable value="#{myData.rows}" var="row">
   <h:column>
      <h:commandLink action="#{myData.doSomething(row)}"/>
   </h:column>
  ...
</h:dataTable>

@Named public class MyData {
   public String doSomething(RowData row) {
      do something with row
      return ...;
   }
}

UIComponent

Some design tools (remember Java Studio Creator?) put all JSF components of a form into a bean, sometimes called the form's “backing bean”. This leads to very messy code, and it encourages application logic programmers to call all sorts of inappropriate UIComponent methods. The UIComponent API is difficult for experts to understand, and it really should not be exposed to application programmers.

One reason you may have for poking around in UIComponent objects is multi-component validation. In that case, get busy and write a custom validator so that you can isolate this hokus-pokus from the rest of your application logic. In JSF 2.0, writing a custom validator isn't as bad as it used to be because the tag handling is so much simpler.

@ManagedBean

So, you managed to get all your beans disentangled from the JSF API, you can test them independently, and you are happy. Then you find that JSF 2.0 has another feature that is just too good to pass up: bean annotations that free you from the tedium of faces-config.xml. No more

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>com.corejsf.UserBean</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

You just use an annotation:

@ManagedBean(name="user")
@RequestScoped
public class UserBean

Except, now you tied your bean once again to the JSF API. The annotations are defined in the javax.faces.bean package.

The solution is not to go back to the tedium of faces-config.xml, but instead to use CDI:

@Named("user")
@RequestScoped
public class UserBean

Ok, so why is this better? First off, @Named is a part of JSR 330, which is tiny and easily integrated in other tools. Of course, @RequestScoped is in javax.enterprise.context, which your test framework would need to support. CDI was built around the assumption that you will wire your beans together in different ways for testing and deployment, and it is just a matter of time before test frameworks buy into that. Or, if you are in a hurry and need to roll your own right now, it makes much more sense to invest in CDI than in JSF support.

Conclusion

One of the key ideas behind JSF is the separation of the visual presentation and the application logic. Admittedly, older versions of JSF have not always made this separation easy or intuitive, but JSF 2.0 changes that. With JSF 2.0, you should be able to write your bean classes without using the JSF API, leaving that API for authoring components. And, if you use composite components, you may never need to see the JSF API at all!