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)
___________________________________________________________

Reply via email to