In your jsp you have
what's the form backing object? I.e., what's the spring : bind class look like, and what's the type of title in it?Code:<form:radiobutton path="title" value="${currentTitle}"/>
In your jsp you have
what's the form backing object? I.e., what's the spring : bind class look like, and what's the type of title in it?Code:<form:radiobutton path="title" value="${currentTitle}"/>
Sorry, I'm confused by my wacky code; I'm using a List of strings for the form. My form search class is SearchCriteria and Affiliations is the enum that I'm using for the check boxes:
Code:public SearchCriteria() { final List<String> affilList = new ArrayList<String>(); final Set<Affiliations> affilsSet = EnumSet.allOf(Affiliations.class); for (final Affiliations affil : affilsSet) affilList.add(affil.name()); affiliations = affilList.toArray(new String[0]); } ... public String[] getAffiliations() { return (affiliations); } public void setAffiliations(final String[] _affiliations) { log.error("affiliations: {}", _affiliations); this.affiliations = _affiliations; }
Ok, my code really is/was wacky; in my jsp I have
Then in my flow definition I haveCode:<form:form modelAttribute="searchCriteria" id="searchForm"> ... <form:checkboxes id="affiliations" path="affiliations" items="${affiliationsList}" /> ...
(To add to the confusion I've changed the name of my class that returns a list of Affiliations enums to the singular AffiliationList from the plural AffiliationsList, and changed the enum from the plural Affiliations to the singular Affiliation, following the convention of naming the enum after the singular, like you're doing as well.)Code:<view-state id="enterSearchCriteria" view="enterSearchCriteria" model="searchCriteria"> <on-render> <evaluate expression="affiliationList.asList()" result="viewScope.affiliationsList" result-type="java.util.List" />
What was confusing me is that in my jsp I'd previously had
and in the flow definition I'd previously hadCode:items="${affiliations}"
So that the name everywhere was, confusingly, "affiliations". But by changing it to affiliationsList it clarifies who's who; affiliationsList is the List of enums, and affiliations is the array of String in my form object SearchCriteria.Code:result="viewScope.affiliations"
So it looks like I'm cheating and using String for what comes back from the form, not enums.
So to make things clear...
I have an enum in a separate class file called Title.java
In a Person class I haveCode:package ro.billoo.app.entity; public enum Title { MR, MS; public String getName() { return toString(); } }
and a Customer classCode:public class Person implements Serializable { long id; Title title; String firstName; String lastName; public Title[] getAvailableTitles() { return Title.values(); } public Title getTitle() { return title; } public void setTitle(Title title) { this.title = title; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
The jsp binding part:Code:public class Customer implements Serializable { long id; Person privateCustomer; public Person getPrivateCustomer() { return privateCustomer; } public void setPrivateCustomer(Person privateCustomer) { this.privateCustomer = privateCustomer; } }
and personForm.jsp that containsCode:<form:form commandName="customer"> <spring:nestedPath path="privateCustomer"> <jsp:include page="/snippets/personForm.jsp"/> </spring:nestedPath> <input type="submit" name="_eventId_submit" value="<spring:message code="common.button.submit"/>"> </form:form>
I registered a conversion service:Code:<c:forEach var="currentTitle" items="${availableTitles}"> <form:radiobutton path="title" value="${currentTitle}"/> <spring:message code="common.label.title-${currentTitle}"/> </c:forEach>
where StringToTitle:Code:public class ApplicationConversionService extends DefaultConversionService { @Override protected void addDefaultConverters() { super.addDefaultConverters(); addConverter(new StringToTitle()); } @Override protected void addDefaultAliases() { super.addDefaultAliases(); addAlias("title", Title.class); } }
With this configuration I get no error but when I submit the form, nothing happends, it doesn't go to the next page. Also I used the debugger to check if toObject or toString are ever called... the answer is no.Code:public class StringToTitle extends StringToObject { public StringToTitle() { super(Title.class); } protected Object toObject(String string, Class objectClass) { return Enum.valueOf(Title.class, string); } protected String toString(Object object) { return object.toString(); } }
What can I do?
I would turn on logging of org.springframework.webflow and org.springframework.binding and see what is logged on postback when your form does not transition properly. Specifically, look for any odd data mapping errors in the log.
Also, did you register your conversion service with the flow configuration system like this?
Where 'conversionService' is the id of your ApplicationConversionService bean.Code:<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices"> <webflow:flow-location path="/WEB-INF/hotels/booking/booking.xml" /> </webflow:flow-registry> <webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" conversion-service="conversionService"/>
Keith Donald
Core Spring Development Team
Thinking about this more...
I suspect what is happening is your StringToTitle converter is running when asked to format a Title Enum for display, but not running when doing the binding on form postback. I believe the reason is because the ConversionService used by the mapping Expression that calls your setTitle method is not your ApplicationConversionService, but rather a DefaultConversionService instance. You can confirm this by using a debugger to introspect the state of your ExpressionParser implementation.
If this is the case, the workaround you'll need to employ is to manually configure your ExpressionParser instance and wire it in as a flow-builder-service. If you are using Ognl, you'll need to configure an instance of WebflowOgnlExpressionParser; if using EL, you'll need to configure an instance of WebflowELExpressionParser. Both of those classes have a conversionService property.
In 2.0.4 we should do a couple of things: first, provide direct support for Java 5 enums in a generic fashion so you don't have to register your own specific converter; second, have the ConversionService used by the default ExpressionParser instances be the same one as you setup in your spring context.
Let me know if you are able to resolve your problem.
Keith
Keith Donald
Core Spring Development Team
Yes I registered it. I also had some problems with the Expression Langauge. I had to add the com.springsource.org.jboss.el-2.0.0.GA.jar library.
I changed the conversion service to
and now my convertor gets added to the customConverter hashmap from the GenericConversionService, but when I try to load the form I still getCode:@Component("conversionService") public class ApplicationConversionService extends DefaultConversionService { @Override protected void addDefaultConverters() { super.addDefaultConverters(); addConverter("title", new StringToTitle()); } }
Code:org.springframework.binding.convert.ConversionExecutorNotFoundException: No ConversionExecutor found for converting from sourceClass [ro.billoo.app.entity.Title] to target class [java.lang.String] org.springframework.binding.convert.service.GenericConversionService.getConversionExecutor(GenericConversionService.java:162) org.springframework.webflow.engine.builder.support.FlowBuilderContextImpl$ParentConversionServiceProxy.getConversionExecutor(FlowBuilderContextImpl.java:132) org.springframework.binding.convert.service.GenericConversionService.getConversionExecutor(GenericConversionService.java:160) org.springframework.webflow.mvc.view.BindingModel.getConverter(BindingModel.java:171) org.springframework.webflow.mvc.view.BindingModel.getFormattedValue(BindingModel.java:144) org.springframework.webflow.mvc.view.BindingModel.getFieldValue(BindingModel.java:128) org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:120) org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:172) org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:192) org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:158)
Well, now your ConversionService is implemented incorrectly. You just registered a custom Converter implementation with id "title" -- that's not what you want; you want the default converter installed to converter between String and Title... just use addConverter(new StringToTitle()) -- see the JavaDocs of addConverter(Converter) and addConverter(String, Converter) for more information.
I think you are close; fixing the bug in your Converter registration and wiring your ApplicationConversionService with a WebFlowELExpressionParser bean should hopefully resolve the problem.
Keith Donald
Core Spring Development Team
Thank you Keith, you were right. It works now, it does the binding
The conversion service:
StringToTitle:Code:public class ApplicationConversionService extends DefaultConversionService { @Override protected void addDefaultConverters() { super.addDefaultConverters(); addConverter(new StringToTitle()); } }
and the configurationCode:public class StringToTitle extends StringToObject { public StringToTitle() { super(Title.class); } protected Object toObject(String string, Class objectClass) { return Title.valueOf(string); } protected String toString(Object object) { return object.toString(); } }
using the com.springsource.org.jboss.el-2.0.0.GA.jar libraryCode:<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" conversion-service="conversionService" expression-parser="expressionParser"/> <bean id="conversionService" class="ro.billoo.app.web.conversion.ApplicationConversionService"/> <bean id="expressionFactory" class="org.jboss.el.ExpressionFactoryImpl"/> <bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser"> <constructor-arg index="0" ref="expressionFactory"/> <property name="conversionService" ref="conversionService"/> </bean>
With the above configuration I noticed another thing
Why does it try to bind to the model the execution parameter from the request? How can I stop it?Code:Caused by: javax.el.PropertyNotFoundException: Property 'execution' not found on type ro.billoo.app.entity.Customer at javax.el.BeanELResolver$BeanProperties.get(BeanELResolver.java:193) at javax.el.BeanELResolver$BeanProperties.access$400(BeanELResolver.java:170) at javax.el.BeanELResolver.property(BeanELResolver.java:279) at javax.el.BeanELResolver.getType(BeanELResolver.java:84) at javax.el.CompositeELResolver.getType(CompositeELResolver.java:112) at org.springframework.binding.expression.el.DefaultELResolver.getType(DefaultELResolver.java:70) at org.jboss.el.parser.AstIdentifier.getType(AstIdentifier.java:32) at org.jboss.el.ValueExpressionImpl.getType(ValueExpressionImpl.java:174) at org.springframework.binding.expression.el.ELExpression.getValueType(ELExpression.java:112) ... 60 more