JSF Select Widgets (a little) Easier

2009/12/17

The implementation of JSF select lists have always confounded me a little. When building your array of SelectItem objects, you are free to put any object as the item’s value. However, simply doing that will lead to confusing / misleading errors. The catch is that you must implement a JSF Converter for the value type in your SelectItem.

I don’t understand why the JSF impl can’t smooth this pattern over. For example, wouldn’t it be enough for the SelectItem values to be uniquely identifiable via a string? Then JSF can simply map them to a the real object stored on the server. Even for a client state saving method, couldn’t JSF serialize the object itself on the client page? Now we are getting somewhere.

The above is really only feasible if your value type is easily transformable to and from a string. If it isn’t, then you are stuck writing comlex serialization / de-serialization routines for any object value that you use in a SelectItem. Why not generalize that? Below is a general-purpose ASCII (and hence web safe) serialization / de-serialization utility.

import com.sun.identity.shared.encode.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class AsciiSerializer {
  public static String serialize(Object o) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream os = new ObjectOutputStream(baos);
    os.writeObject(o);
    os.flush();
    os.close();

    String es = Base64.encode(baos.toByteArray());
    return es;
  }

  public static Object deserialize(String es) throws IOException, ClassNotFoundException {
    byte[] ba = Base64.decode(es);
    ByteArrayInputStream bais = new ByteArrayInputStream(ba);
    ObjectInputStream is = new ObjectInputStream(bais);

    Object o = is.readObject();
    is.close();

    return o;
  }
}

You still must write and register a converter, but now it is trivial,

public class MyConverter implements Converter {
  public Object getAsObject(FacesContext context, UIComponent component, String value) {
    MyObject mo = AsciiSerializer.deserialize(value);
    return mo;
  }

  public String getAsString(FacesContext context, UIComponent component, Object value) {
    return AsciiSerializer.serialize(value);
  }
}