Tuesday, July 20, 2010

Using JSF Conversion with Custom Objects

My goal is to provide a user-facing input text box that accepts a comma-separated list of values, and converts these to an application-specific object. Since I'm using JSF, I'll take advantage of their built-in conversion facility - but I want to remain as decoupled from JSF as possible. This means I'll implement their Converter class, but I'll do so only with very minimal high-level business logic (as opposed to emulating their example usage in the JEE Tutorial example). That way, I can reuse my conversion logic in other frameworks, for other clients, etc.

My application object is a collection of name objects. The name objects look something like this:
public class MyName {   

    private String name;

    public MyName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean equals(Object obj) {
        if (null == obj) {
            return true;
        }

        if ((obj == null) || (getClass() != obj.getClass())) {
            return false;
        }

        MyName other = (MyName) obj;
        return new EqualsBuilder().append(this.name, other.name).isEquals();
    }

    public int hashCode() {
        return new HashCodeBuilder(3, 13).append(this.name).toHashCode();
    }

    public String toString() {
        return name;
    }
}
Note that I've implemented toString as well as the equals and hashCode methods. The builder classes are provided by Apache Commons Lang; I've blogged about them here.

The collection object uses the Java List to wrap the name objects:
public class MyNameList {

    private List<MyName> names = new ArrayList<MyName>();

    public MyNameList (List<MyName> names) {
        setNames(names);
    }

    public static MyNameList getInstance(String csv) {
        if (csv == null) {
            return new MyNameList();
        }
        String[] names = csv.split(",");
        List<MyName> nameList = new ArrayList();
        for (String name : names) {
            nameList.add(new MyName(name));
        }
        return new MyNameList(nameList );
    }

    public List<MyName> getNames() {
        return names;
    }

    public void setNames(List<MyName> names) {
        this.names = names;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        String comma = "";
        for (MyName name: names) {
            sb.append(comma).append(name);
            comma = ",";
        }
        return sb.toString();
    }
}

Here's what the JSF converter implementation might look like - there's not much there, as planned:
public class MyNameListConverter implements Converter {

    public Object getAsObject(FacesContext context,
                              UIComponent component, String newValue)
            throws ConverterException {

        return MyNameList.getInstance(newValue);
    }

    public String getAsString(FacesContext context,
                              UIComponent component, Object value)
            throws ConverterException {

        return value == null? "" : value.toString();
    }
}
I must register the converter with JSF:
<converter>
    <description>
        Converter for CSV list of name values
    </description>
    <converter-id>MyNameListConverter</converter-id>
    <converter-class>
        com.mybiz.MyNameListConverter
    </converter-class>
</converter>
Finally I reference the converter in my JSF page:
 <ice:inputText id="nameValues" partialSubmit="true"
     converter="MyNameListConverter"
     value="#{bean.nameList}"/>

Lots of moving parts are needed when working with JSF. But, the more I can encapsulate, the less it will cost to migrate to a different web framework down the road.

No comments:

Post a Comment