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>
 
     
     
    