Automatic to-Object conversion in JSF selectOneMenu & Co.

21 December 2011, by: Development

When creating a web UI, there is often the need to let a user make a selection from e.g. a drop-down. The items in such a drop-down are not rarely backed by domain objects. Since these items in the HTML rendering are just simple character identifiers, we need some way to encode the object version of an item to this simple character identifier (its string representation) and back again.

A well established pattern in JSF is the following:

  1. <selectOneMenu value="#{bean.selectedUser}" converter="#{userConverter}">
  2.     <selectItems value="#{bean.selectableUsers}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
  3. </h:selectOneMenu>

With userConverter defined as something like:

  1. @ManagedBean(name="userConverter")
  2. public class UserConverter implements Converter {
  3.  
  4.     @EJB
  5.     private UserDAO userDAO;
  6.  
  7.     @Override
  8.     public Object getAsObject(FacesContext context, UIComponent component, String value) {
  9.         return userDAO.getById(Long.valueOf(value));
  10.     }
  11.  
  12.     @Override
  13.     public String getAsString(FacesContext context, UIComponent component, Object value) {
  14.         return ((User) value).getId().toString();
  15.     }
  16. }

(note that the converter implementation is overly simplified, in practice null checks and checks for “String value” representing a number would be needed)

This is functional, but in its full form not really convenient to program nor efficient. Upon every post back, there will be a call to the DAO to convert the string representation of an item back to its Object form. When no caching is used or the object isn’t in the cache, this will likely result in a call to a back-end database, which is never a positive contribution to the overall performance of a page. It gets worse when there are more of such conversions being done and it can go through the roof when we have e.g. editable tables where each row contains multiple drop-downs. A table with only 10 rows and 4 drop-downs might result in 40 separate calls to the back-end being done.

But is using a DAO here really needed?

After all, after the converter does its work, JSF checks whether the Object is equal to any of the Objects in the collection that was used to render the drop-down. In other words, the target Object is already there! And it must be there by definition, since without it being present validation will simply never pass.

So, by taking advantage of this already present collection we can prevent the DAO calls. The only question is, how do we get to this from a converter? A simple way is to use a parameter of some kind and provide it with a binding to this collection. This is however not really DRY, as it means we have to bind to the same collection twice right after each other (once for the selectItems, once for the converter parameter). Additionally, we still have to iterate over this and need custom code that knows to which property of the Object we need to compare the String value. For instance, for our User object we need to convert the String value to a Long first and then compare it with the Id property of each instance.

Another approach that I would like to present here is basically emulating how a UIDataTable detects on which row a user did some action. After the post back it iterates over the data that was used to render the table in the same way again. This will cause the same IDs to be generated as in the original rendering. The ID of the component that is posted back is compared to the newly generated one and when they match iteration stops and we know the row.

In this case, after the post back we’ll iterate over all select item values, convert these to their string representation and compare that to the ‘to-be-converted’ string value. If they match, the unconverted object is the one we’re after and we return that. For iterating over the select item values, I took advantage of a private utility class that’s in Mojarra: com.sun.faces.renderkit.SelectItemsIterator (for a proof of concept, I just copied it since it’s package private).

The implementation is done via a Converter base class from which user code can inherit. That way, only a getAsString method needs to be implemented:

  1. @ManagedBean(name="userConverter")
  2. public class UserConverter extends SelectItemsBaseConverter {
  3.     @Override
  4.     public String getAsString(FacesContext context, UIComponent component, Object value) {
  5.         return ((User) value).getId().toString();
  6.     }
  7. }

The base class is implemented as follows:

  1. public abstract class SelectItemsBaseConverter implements Converter {
  2.     @Override
  3.     public Object getAsObject(FacesContext context, UIComponent component, String value) {        
  4.         return SelectItemsUtils.findValueByStringConversion(context, component, value, this);    
  5.     }    
  6. }

And the SelectItemsUtils class:

  1. public final class SelectItemsUtils {
  2.  
  3.     private SelectItemsUtils() {}
  4.  
  5.     public static Object findValueByStringConversion(FacesContext context, UIComponent component, String value, Converter converter) {
  6.         return findValueByStringConversion(context, component, new SelectItemsIterator(context, component), value, converter);        
  7.     }
  8.  
  9.     private static Object findValueByStringConversion(FacesContext context, UIComponent component, Iterator<SelectItem> items, String value, Converter converter) {
  10.         while (items.hasNext()) {
  11.             SelectItem item = items.next();
  12.             if (item instanceof SelectItemGroup) {
  13.                 SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
  14.                 if (!isEmpty(subitems)) {
  15.                     Object object = findValueByStringConversion(context, component, new ArrayIterator(subitems), value, converter);
  16.                     if (object != null) {
  17.                         return object;
  18.                     }
  19.                 }
  20.             } else if (!item.isNoSelectionOption() && value.equals(converter.getAsString(context, component, item.getValue()))) {
  21.                 return item.getValue();
  22.             }
  23.         }        
  24.         return null;
  25.     }
  26.  
  27.     public static boolean isEmpty(Object[] array) {
  28.         return array == null || array.length == 0;    
  29.     }
  30.  
  31.     /**
  32.      * This class is based on Mojarra version
  33.      */
  34.     static class ArrayIterator implements Iterator<SelectItem> {
  35.  
  36.         public ArrayIterator(SelectItem items[]) {
  37.             this.items = items;
  38.         }
  39.  
  40.         private SelectItem items[];
  41.         private int index = 0;
  42.  
  43.         public boolean hasNext() {
  44.             return (index < items.length);
  45.         }
  46.  
  47.         public SelectItem next() {
  48.             try {
  49.                 return (items[index++]);
  50.             }
  51.             catch (IndexOutOfBoundsException e) {
  52.                 throw new NoSuchElementException();
  53.             }
  54.         }
  55.  
  56.         public void remove() {
  57.             throw new UnsupportedOperationException();
  58.         }
  59.     }
  60. }

The source of the Mojarra SelectItemsIterator can be found here, MyFaces seems to have a similar implementation here, but I did not test that one yet. PrimeFaces also has something like this (see org.primefaces.util.ComponentUtils#createSelectItems).

A custom selectOneMenu, selectManyListbox etc could even go a step further and should be able to do this without the need for a converter at all. Combined with an extended selectItems tag, it could look like this:

  1. <selectOneMenu value="#{bean.selectedUser}">
  2.     <selectItems value="#{bean.selectableUsers}" var="user" itemValue="#{user}" itemValueAsString="#{user.id}" itemLabel="#{user.name}" />
  3. </h:selectOneMenu>

In this hypothetical example, we could even simplify stuff further by setting itemValue by default to var, so it would then look like this:

  1. <h:selectOneMenu value="#{bean.selectedUser}">
  2.     <f:selectItems value="#{bean.selectableUsers}" var="user" itemValueAsString="#{user.id}" itemLabel="#{user.name}" />
  3. </h:selectOneMenu>

Implementing this might be the topic for a next article. For now I'll hope the converter based approach is useful.

An implementation of this is readily available in the new OmniFaces library, from where you can find the documentation and the full source code. There's also a live demo available.

Arjan Tijms

10 comments to “Automatic to-Object conversion in JSF selectOneMenu & Co.”

  1. samy omar says:

    Thanks a million, You solved big issue faced me this week.
    Also I will wait your new article of implementing such custom tags to be easier to use the selected row as Object.

  2. Integrating Spring & JavaServer Faces : Select Items « Phil Webb's Blog says:

    [...] that, when converted to a String, matches your submitted value. You can read more about the idea in this blog post by Arjan Tijms. Using this technique with the <s:selectItems> component is really easy, [...]

  3. Anonymous says:

    ^^ Sorry about the above comment, WordPress seems to have automatically added it when I linked to your article.

    Thanks for the post.

  4. arjan tijms says:

    No problem, thanks for the link! I like your series ;)

  5. Shirish says:

    Hi Arjan,

    I have tried to implement “Google suggest” using primefaces Autocomplete component. User will type School Id, application will display list of matching school Ids. In case, User selects or types existing school id, application will prepopulate other fields on screen for editing purpose.
    If no match found, Application will create new school record after filling required data.

    For this purpose, I have created new school object in converter, if schood Id is not found in DB. It works well, but problem is getAsObject method is getting call multiple times hence initially created object is lost/many such objects are getting created.

    To avoid this, I maintained list of object in converter. if object is not in DB, we will check for existance of object in List using school ID. We will add object to list along with ID if it is not found in list. Great, this works well as excepted. But during testing, I found that Objects added to list are accessible from totally different browser session on different machine. :(

    Not sure what is wrong in this approach..

    Regards
    Shirish

  6. Fesal says:

    Thanks a lot for this great example, you saved my time. God rewards you!

  7. Rob says:

    Thanks for this clear example. I really like this solution and would like to implement it in our project.

    I copied your code and implemented it. Problem is that I keep getting the following exception:

    http://pastebin.com/hnmUeyx8
    tried to access class javax.faces.component.SelectItemsIterator from class javax.faces.component.SelectItemsIterator$4

    I can’t find anything on this error, do you know a solution?

  8. arjan tijms says:

    Rob, the exception is about the private implementation class javax.faces.component.SelectItemsIterator. The class has package private access and can thus not be instantiated from user code.

    If you want to use it from user code, you would have to copy it (from e.g. the link I provided) and put it in a package of your own. If you do that, please note that this is fine for internal use but if you want to distribute the result you have to take into account the license.

    In OmniFaces I circumvented this problem by implementing a way to collect all select items without depending on the private Mojarra version, see org.omnifaces.util.selectitems.

  9. Rob says:

    Thanks alot, I managed to got it running now.

  10. Anonymous says:

    :)

Type your comment below:


− two = 3

css.php best counter