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