NOTE: This started out as a question, but ended up being a great way to talk myself through identifying the issue. Since I'd already written it all up, I'm posting anyway in case it helps someone. I have seen other workarounds to this error, but this eliminates hand-coding Converters & leverages existing Roo generated artifacts - it seems the right way to me.
The experience also suggests some potential improvements to Roo WebFlow integration:
- Register the ConversionServiceExposingInterceptor with FlowHandlerMapping by default when generating webflow-config.xml
- Remove the AnnotationMethodHandlerAdapter bean definition from webflow-config.xml. This bean is already registered by the <mvc:annotation-driven> magic.
- Remove the SimpleControllerHandlerAdapter bean definition from webflow-config.xml. Roo uses Annotation-based Controllers by default - if someone wants to use concrete Controller implementations, they can explicitly add this bean definition back, and probably put it in webmvc-config.xml where it belongs.
Anyway, here's the details:
I am getting an error when rendering a Roo entity using the autogenerated show.jspx view from a Web Flow. The error is:
I think this is not a Roo-specific issue; however, the Roo-generated Controllers do not exhibit the issue so I though it was worth posting here.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from 'java.util.Set' to 'java.lang.String'
I have a working Roo project with a couple of entities, including OneToMany and ManyToMany relationships between some of them. For instance I have a Metric entity and a PracticeItem entity that has a Set<Metric> of "supportedMetrics", marked @ManyToMany.
The Roo-generated MVC controllers & views render these entities properly. I have created a flow for PracticeItems & copied across the autogenerated CRUD view JSPX files into the flow folder. I configured the flow's views.xml to point at these copies. Finally, I've edited the flow to make the PracticeItem entity instance available to the view in requestScope. When navigating to this view via the flow, I get the above error.
After some research, I found that WebFlow configures its own ConversionService in the FlowBuilderServices bean. Further this is a Spring Binding ConversionService that has a different package name & method signatures from the Spring Core ConversionService. Roo has auto-generated an ApplicationConversionServiceFactoryBean of the latter (Spring Core) type that includes custom Converters for PracticeItem and Metric entities.
I tried creating a Spring Binding DefaultConversionService which can delegate to a Spring Core ConversionService passed in to the constructor; I wired this to FlowBuilderServices. This did not help.
Remote debugging through the code, I eventually found that the problem originates in the Spring EvalTag used in the Roo-generated display.tagx tag handler. That eval tag is used to render the entities property like using a Spring EL expression:
It appears that sometimes that tag expects to retrieve the ConversionService from the pageContext/request. If it's not there, it creates a default one - without the custom converters. That was the origin of my problem.
<spring:eval expression="object[field]" />
With further research I found that the <mvc:annotation-driven /> magic registers a ConversionServiceExposingInterceptor which does what it says, and makes sure that ConversionService is always available on the request (docs point out specifically that eval tag relies on this). This is great - but it only does this for MVC HandlerMappings!
SOLUTION: contstruct an instance of ConversionServiceExposingInterceptor, passing in Roo-generated ApplicationConversionService. Register this intercepter with FlowHandlerMapping's "interceptors" property. A fragment of webflow-config.xml is below. Note that applicationConversionService is the Roo-generated artifact defined in webmvc-config.xml
<bean id="conversionServiceExposingInterceptor" class="org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor">
<constructor-arg ref="applicationConversionService" />
<property name="order" value="0" />
<property name="flowRegistry" ref="flowRegistry" />
<!-- NOTE: commenting out these as they appear unnecessary!!! -->
<!--Dispatches requests mapped to POJO @Controllers implementations-->
<!-- <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> -->
<!-- Dispatches requests mapped to org.springframework.web.servlet.mvc.Controller implementations -->
<!-- <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> -->