Generic JSF Converter for Seam

November 16th, 2008 Radim Marek Posted in seam |

JBoss Seam provides excellent support for mapping of the managed entities back and forth between the select items and the actual entities. Tag s:convertEntity supplies JSF converter which renders option values and consequently translate them back, using Entity Loader (either Hibernate Session or EntityManager), to the appropriate entity. Whole process relies on the provision of the entity identifier. This is indeed very powerful solution, but unfortunately so far I’ve haven’t got much chances to use it, except the training or with very simple applications. In most scenarios, the problem comes with strict separation of the presentation layer from business logic and therefore persistence support. And without Entity Loader, there’s not much fun with s:convertEntity.

Solution to this problem is quite simple - introduction of the custom JSF converter, where only requirement is to implement javax.faces.convert.Converter interface. There’s plenty of examples available on-line or you can refer to Chapter 15.5 of JSF In Action. If it’s for a single use, this is perfect solution. A bit more systematic approach to keep the code manageable is to create a generic converter.

This particular solution is based on the assumption there’s a collection of objects of the same class, and the need for identifier distinguishing these instances. Following the same approach as with s:convertEntity the first step would be to introduce thecustom Facelet tag.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib xmlns="http://java.sun.com/JSF/Facelet">
  <namespace>http://laststation.net/utils/jsf</namespace>

  <tag>
    <tag-name>convertGeneric</tag-name>
    <converter>
	  <converter-id>net.laststation.utils.jsf.converter.GenericConverter</converter-id>
    </converter>
  </tag>
</facelet-taglib>	

Save this file as META-INF/cg.taglib.xml and using namespace you can refer it within your XHTML view. Next step is to implement GenericConverter. Example follows.

package net.laststation.utils.jsf.converter;

import static org.jboss.seam.ScopeType.STATELESS;
import static org.jboss.seam.annotations.Install.FRAMEWORK;

import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.faces.Converter;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

import java.io.Serializable;
import java.util.Collection;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

/**
 * Supports conversion of an object to/from an object
 *
 * @author radimm
 */
@Name("net.laststation.utils.jsf.converter.GenericConverter")
@Scope(STATELESS)
@Install(precedence = FRAMEWORK)
@Converter
@BypassInterceptors
public class GenericConverter implements javax.faces.convert.Converter {
    private Log log = LogFactory.getLog(GenericConverter.class);
    private String identifier;
    private Collection collection;

    public Object getAsObject(FacesContext ctx, UIComponent component, String s) {
        if (s == null)
            return null;

        if (collection != null) {
            for (Object item : collection) {
                String id = getItemIdentifier(item, s);

                if (id != null && id.equals(s)) {
                    return item;
                }
            }
        }

        return null;
    }

    public String getAsString(FacesContext ctx, UIComponent component, Object o) {
        return getItemIdentifier(o, identifier);
    }

    protected String getItemIdentifier(Object o, String property) {
        PropertyDescriptor desc;
        Object result;

        try {
            desc = new PropertyDescriptor(property, o.getClass());
            result = desc.getReadMethod().invoke(o);

            return result.toString();
        } catch (Throwable e) {
            log.error("Unable to get object identifier!", e);
        }

        return null;
    }

    public Collection getCollection() {
        return collection;
    }

    public void setCollection(Collection collection) {
        this.collection = collection;
    }

    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }
}

Implementation is quite straightforward, following the same principles as I’ve shown in the post Extending Seam components. It’s important to keep this component stateless (or at least bound them to EVENT scope) to make sure the supplied collection and the identifier are used only within a single call. Provided code is nothing close to perfect, it’s supposed to serve as an example to various different implementations. Also it’s supposed to be an example how easy is to write custom Facelet tag.

To use this converter just place code similar to following example where appropriate:

		<cg:convertGeneric collection="#{mycomponent.countries}" identifier="code"/>
	

Where countries is collection like List<Country> countries, and code is property of the objects within it. Using all the power of EL expressions and Seam functionality - like factories - there’s a plenty of use cases for this converter.

Complete source code is available for download as maven project (see links below). This example omits use of JSPs, because I hope nobody is really using them these days - especially with Seam projects.

Links:


Leave a Reply