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>