... Or maybe you're clueless about software development all together and stalking me for pleasure. Either way, welcome and read on!
The Universal JSF Converter
One converter to rule them all...
I've developed a single converter that can be applied to any JSF UISelect component requiring one. For example, selectOneMenu, selectManyListbox, selectOneRadio, etc etc.... I've developed a rough crack at what JSF should have included from the get go so that developers don't need to write converter code, converting their business objects into Strings for representation in a menu.
So we've got a list of Objects that we want to show in a SelectItem list. Some String value will need to be used in the "value" attribute of the rendered element.
<option value="?????" >Employee 1</option>
Why should we care what value is used?? Why doesn't JSF just assign some random identifier to each object? Really, there's no need to write a different converter for each object, depending on that object's primary key value, or other data. Any object can be converted to a String to display on the screen and then converted back into an object after being selected. Why does JSF force us to write a converter? There should be a better default converter. A universal default converter.
Enough hubbub, here's the solution: JsfUniversalConverter.java
private static final String objectCacheKey = "JSF_UNIVERSAL_CONVERTER_OBJECT_CACHE";
@Override
public Object getAsObject(FacesContext fc, UIComponent uic, String string) {
if (string == null || string.length() == 0) {
return null;
}
Object returnObject = getObjectCache(fc).get(string);
return returnObject;
}
@Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
if (o == null) {
return "";
}
String returnString = null;
MapobjectCache = getObjectCache(fc);
//search cacheMap to see if this has already been converted.
for (Map.EntrycacheEntry : objectCache.entrySet()) {
Object cachedObject = cacheEntry.getValue();
if (cachedObject==null) continue;
if ( o.equals(cachedObject) || o==cachedObject ) {
returnString = cacheEntry.getKey();
}
}
if (returnString==null) {
returnString = UUID.randomUUID().toString();
objectCache.put(returnString, o);
}
return returnString;
}
private MapgetObjectCache(FacesContext fc) {
HttpSession session = (HttpSession) fc.getExternalContext().getSession(true);
Object object = session.getAttribute(objectCacheKey);
if (object!=null && object instanceof Map) {
return (Map) object;
} else {
MapobjectCache = new HashMap ();
session.setAttribute(objectCacheKey, objectCache);
return objectCache;
}
}
Be sure to plug this into your faces-config.xml:
<converter>
<converter-id>universal</converter-id>
<converter-class>my.package.JsfUniversalConverter</converter-class>
</converter>
And if you're really good, just build a jar file that contains the class and faces-config.xml, and include this jar file on every JSF project.
From here on out, you solve all of your menu conversion issues with ONE converter:
<h:selectOneMenu converter="universal" ...Caveat: If you use this and receive "invalid value" errors:
- Remember to properly implement the equals and hashcode methods of your business objects
OR
- Ensure that your managed bean providing the List
is returning the exact same instances of the objects in the SelectItem value every time.
Exciting? Thank me by leaving a comment. Your comments are what I do all this for. Thanks!
No comments:
Post a Comment