Java 1.5
MyFaces 1.1.7
Tomahawk 1.1.9
Spring 2.5.6
Weblogic 9.2.3
All I wanted to do was display a drop-down list of enums. It seemed simple
enough, with examples all over the 'net. Apparently, myfaces has decided that
using anything other than strings for a drop down is "no - not yours".
The setup:
My example enum:
------------------------------------------------------------
package com.facets;
import java.util.Arrays;
import java.util.List;
public enum DaysOfWeek {
SUNDAY("Sunday"),
MONDAY("Monday"),
TUESDAY("Tuesday"),
WEDNESDAY("Wednesday"),
THURSDAY("Thursday"),
FRIDAY("Friday"),
SATURDAY("Saturday");
private static List<DaysOfWeek> dayList =
Arrays.asList(DaysOfWeek.values());
private String label;
private DaysOfWeek(String label) {
this.label = label;
}
public String getLabel() {
return this.label;
}
public static DaysOfWeek getByLabel(String label) {
for(DaysOfWeek day : dayList) {
if(day.getLabel().equals(label)) {
return day;
}
}
return null;
}
public static final List<DaysOfWeek> getAllList() {
return dayList;
}
}
------------------------------------------------------------
JSF Converter:
------------------------------------------------------------
package com.facets;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
public class DayEnumConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component,
String value)
throws ConverterException {
DaysOfWeek day = DaysOfWeek.getByLabel(value);
return (day==null ? DaysOfWeek.SUNDAY : day);
}
public String getAsString(FacesContext context, UIComponent component,
Object object)
throws ConverterException {
if(object instanceof String) {
return (String)object;
}
if(!(object instanceof DaysOfWeek)) {
return "";
}
return ((DaysOfWeek)object).getLabel();
}
}
------------------------------------------------------------
Spring definition to get a list of the enums:
------------------------------------------------------------
<bean id="daysEnumList"
class="com.facets.DaysOfWeek"
factory-method="getAllList" />
------------------------------------------------------------
A simple drop down list made from an ArrayList of Enums.
------------------------------------------------------------
<h:outputLabel for="daysOfWeekList" value="#{labels.dayList}" />
<t:selectOneMenu id="daysOfWeekList"
value="#{calendar.day}">
<t:selectItems var="day"
value="#{daysEnumList}"
itemLabel="#{day.label}"
itemValue="#{day.label}"/>
<f:converter converterId="dayEnumConverter"/>
</t:selectOneMenu>
------------------------------------------------------------
Assumptions:
1) I'm using SpringBeanVariableResolver to get the "daysEnumList" object from
the spring application context.
2) The problem goes away if I take the daysOfWeekList selectOneMenu control off
the jsp page. Also, there is another dropdown list using only strings and that
works just fine.
3) The converter is properly listed in the faces-context.xml file, as it is
called normally during the JSF lifecycle - at least until the bug hits.
The Bug:
When I submit a form containing the above selectOneMenu control, the list of
which is created from a Java 5 Enum, I get this error in the logs:
------------------------------------------------------------
DEBUG | 2010-01-06 13:28:53,912 | LifecycleImpl.java:178 | exiting from
lifecycle.execute in RESTORE_VIEW(1) because getRenderResponse is true from one
of the after listeners
------------------------------------------------------------
This means that backing bean never gets bound to the form values, and the form
action is never called. I never get past the "Apply Request Values" step of
the faces lifecycle.
Take a look at this stack trace...
------------------------------------------------------------
Daemon Thread [[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default
(self-tuning)'] (Suspended)
_SelectItemsUtil.matchValue(Object, Iterator) line: 65
HtmlSelectOneMenu(UISelectOne).validateValue(FacesContext, Object) line: 77
HtmlSelectOneMenu(UIInput).validate(FacesContext) line: 428
HtmlSelectOneMenu(UIInput).processValidators(FacesContext) line: 245
HtmlTag(UIComponentBase).processValidators(FacesContext) line: 866
HtmlForm(UIForm).processValidators(FacesContext) line: 78
UIViewRoot(UIComponentBase).processValidators(FacesContext) line: 866
UIViewRoot.processValidators(FacesContext) line: 169
ProcessValidationsExecutor.execute(FacesContext) line: 32
LifecycleImpl.executePhase(FacesContext, PhaseExecutor, PhaseListenerManager)
line: 105
LifecycleImpl.execute(FacesContext) line: 80
FacesServlet.service(ServletRequest, ServletResponse) line: 143
StubSecurityHelper$ServletServiceAction.run() line: 225
StubSecurityHelper.invokeServlet(ServletRequest, HttpServletRequest,
ServletRequestImpl, ServletResponse, HttpServletResponse, Servlet) line: 127
ServletStubImpl.execute(ServletRequest, ServletResponse, FilterChainImpl)
line: 283
TailFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 26
FilterChainImpl.doFilter(ServletRequest, ServletResponse) line: 42
ExtensionsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line:
246
FilterChainImpl.doFilter(ServletRequest, ServletResponse) line: 42
NavigationFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line:
93
DelegatingFilterProxy.invokeDelegate(Filter, ServletRequest, ServletResponse,
FilterChain) line: 236
DelegatingFilterProxy.doFilter(ServletRequest, ServletResponse, FilterChain)
line: 167
FilterChainImpl.doFilter(ServletRequest, ServletResponse) line: 42
ExtensionsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line:
301
FilterChainImpl.doFilter(ServletRequest, ServletResponse) line: 42
WebAppServletContext$ServletInvocationAction.run() line: 3212
AuthenticatedSubject.doAs(AbstractSubject, PrivilegedAction) line: 321
SecurityManager.runAs(AuthenticatedSubject, AuthenticatedSubject,
PrivilegedAction) line: 121
WebAppServletContext.securedExecute(HttpServletRequest, HttpServletResponse,
boolean) line: 1983
WebAppServletContext.execute(ServletRequestImpl, ServletResponseImpl) line:
1890
ServletRequestImpl.run() line: 1344
ExecuteThread.execute(Runnable) line: 209
ExecuteThread.run() line: 181
------------------------------------------------------------
The bug is at line 65 of _SelectItemsUtil.
------------------------------------------------------------
44 public static boolean matchValue(Object value,
45 Iterator selectItemsIter)
46 {
47 while (selectItemsIter.hasNext())
48 {
49 SelectItem item = (SelectItem) selectItemsIter.next();
50 if (item instanceof SelectItemGroup)
51 {
52 SelectItemGroup itemgroup = (SelectItemGroup) item;
53 SelectItem[] selectItems = itemgroup.getSelectItems();
54 if (selectItems != null
55 && selectItems.length > 0
56 && matchValue(value, Arrays.asList(
57 selectItems).iterator()))
58 {
59 return true;
60 }
61 }
62 else
63 {
64 Object itemValue = item.getValue();
65 if (value==itemValue || (itemValue.equals(value)))
66 {
67 return true;
68 }
69 }
70 }
71 return false;
72 }
------------------------------------------------------------
The problem is that at this point, the "value" is the value that was selected
in the dropdown. The CONVERTED value - compliments of line 428 in the UIInput
class:
------------------------------------------------------------
424 Object convertedValue = getConvertedValue(context, submittedValue);
425
426 if (!isValid()) return;
427
428 validateValue(context, convertedValue);
------------------------------------------------------------
BUT - the "item" object is from an Iterator created on line 77 of UISelectOne,
which only has the string values of the select component.
This means when "item.getValue()" is called on line 64 (above), you are getting
the string value from whatever selectitem entry is currently targeted - and of
course the object version of the selected value isn't going to equal its
non-converted string value.
Either I'm missing something and there's some configuration I missed to fix
this (which would make this behaviour a horrible default), or this is a pretty
big bug that just cost me five hours of development time.
If no one cares about JSF 1.1 implementations anymore, just let me know and
I'll not spend the time posting here. Otherwise, any chance this could be
fixed?
___________________________________________________________
John O'Grady
Dragon Tamer
Human beings, who are almost unique in having the ability to learn
from the experience of others, are also remarkable for their apparent
disinclination to do so.
- Douglas Adams
Those who do not learn from history are doomed to repeat it.
- George Santayana
Qui tacet consentit
(Silence implies consent)
___________________________________________________________