whew! finally, a pattern for SelectItems using generics
patrick_ibg Oct 19, 2005 5:49 PMTo use jsf <f:selectItems> that use values backed by a persistent entity, do the following... I'm using 'State' as my persistent entity.
First, I make State implement Lookup:
public interface Lookup { public String getKey () ; public String getName () ; }
Where getKey is simply an alias for getId. (For entities with Long id's, simply return id.toString()).
Next I create a SEAM managed POJO called SelectItems that lives in the application context:
@Name ("selectItems") @Scope (APPLICATION) @Intercept (ALWAYS) public class SelectItems { private static final Logger log = Logger.getLogger (SelectItems.class); @In (create = true) private StateDao stateDao ; private Map<Class<? extends Lookup>, Map<String, ? extends Lookup>> entityMapMap = new HashMap<Class<? extends Lookup>, Map<String, ? extends Lookup>> () ; private Map<Class<? extends Lookup>, List<SelectItem>> selectItemListMap = new HashMap<Class<? extends Lookup>, List<SelectItem>> () ; public SelectItems () { } @Create public void onCreate () { log.info ("Initializing SelectItems") ; List<State> stateList = stateDao.list () ; addLookup (State.class, stateList) ; return ; } private <T extends Lookup> void addLookup (Class<T> entityClass, List<T> entityList) { // add entityMap Map<String, T> entityMap = new HashMap <String, T> () ; for (T entity : entityList) { Lookup l = (Lookup) entity ; entityMap.put (l.getKey(), entity) ; } entityMapMap.put (entityClass, entityMap) ; // add selectItemList List<SelectItem> selectItemList = new ArrayList<SelectItem> () ; for (T entity : entityList) { Lookup l = (Lookup) entity ; SelectItem item = new SelectItem (entity, l.getKey(), l.getName()) ; selectItemList.add (item) ; } selectItemListMap.put (entityClass, selectItemList) ; } public <T extends Lookup> Map<String, T> getEntityMap (Class<T> entityClass) { return (Map<String, T>) entityMapMap.get (entityClass) ; } public <T extends Lookup> List<SelectItem> getSelectItemList (Class<T> entityClass) { return selectItemListMap.get (entityClass) ; } public List<SelectItem> getStates () { return getSelectItemList (State.class) ; } @Destroy public void destroy() { // not sure if this is required for application context log.info ("destroyed"); } }
Note the onCreate method, which registers all the lookup entities. Add your lookup entities here as you create them. SEAM will inject the DAO objects. StateDao is a SLSB that injects its own PersistenceContext.
SelectItems does two things: it creates a list of SelectItem objects for the drop down. It also creates an in-memory map of the lookup entities for use by a Converter class. It puts both in their respective HashMaps.
Next we have a generic converter class:
public abstract class LookupConverter<T extends Lookup> implements javax.faces.convert.Converter { private Class<T> entityClass ; public LookupConverter (Class<T> entityClass) { this.entityClass = entityClass ; } public String getAsString (FacesContext context, UIComponent component, Object object) { if (object == null) return null ; T lookup = (T) object ; return lookup.getKey () ; } public Object getAsObject (FacesContext context, UIComponent component, String value) { if (value == null) return null ; // get selectItems from application context Context ctx = Contexts.getApplicationContext () ; SelectItems selectItems = (SelectItems) ctx.get ("selectItems") ; if (selectItems == null) { // create selectItems in application context if not found selectItems = (SelectItems) Component.newInstance ("selectItems") ; ctx.set ("selectItems", selectItems) ; } // get the entityMap for this entityClass Map<String, T> entityMap = selectItems.getEntityMap (entityClass) ; if (entityMap == null) { // throw a non-recoverable exception throw new ConverterException ("Unable to get entity map for " + entityClass.getCanonicalName()) ; } T object = entityMap.get (value) ; if (object == null) { // throw a recoverable exception throw new ConverterException (new FacesMessage (component.getId(), value + " is an invalid value")) ; } return object ; } }
Each entity class will have to extend this generic class as follows:
public class StateConverter extends LookupConverter<State> { public StateConverter () { super (State.class) ; } }
Then you just register the converter in faces-config.xml, and use it in your view markup as follows:
<div class="input"> <h:selectOneMenu id="state" value="#{customer.address.state}" converter="StateConverter"> <f:selectItems value="#{selectItems.states}" /> </h:selectOneMenu> <br/><span class="errors"><h:message for="state" /></span> </div>