Monday, March 28, 2011

FF obeys the rules and Spring 3 chokes

It's really quite sad/annoying when a toolkit as sophisticated as Spring chokes on simple matters.

I'm writing a GWT client to make RPC (XML/HTTP) calls to the server which is implemented in Spring MVC. My Controller has a @RequestBody on the input type which is a class that is annotated with JAXB annotations to serialise/deserialise from XML. Therefore my Controller is at the mercy of the HttpMessageConverter that Spring uses to rip the data out of the HTTP request, turn it into a POJO and give it to my Controller.

In the Spring config (I use the XML config), the <mvc:annotation-driven/> by default registers an instance of AnnotationMethodHandlerAdapter which (among other duties) is responsible for determining which message converter to use. What is nice is that a JAXB converter is registered automatically if the JAXB libs are on the classpath. The converter that is chosen is the one that can process the MIME type or Content-Type that is in the HTTP request header. In this case it would be application/xml which is processed by the default MarshallingHttpMessageConverter which in turn delegates the real work to JAXB.

However Firefox (FF) obeys the rules regarding XHR requests, in that it appends to the Content-Type header a charset. So application/xml becomes application/xml; charset=UTF-8. Because of this the entire server side code unravels because the Content-Type is not smart enough to parse the charset out of the Content-Type header; and throws an exception that the Content-Type is not recognised.

The solution after much reading up on the internals of the Spring MVC framework is to create my own bean tree where the supported type contains the charset. The message converters support changing the supported MIME types, which are modelled by the class MediaType. MediaType supports a Charset in its constructor. Therefore I end up with the following config.

<!-- Override default AnnotationMethodHandlerAdapter that
mvc:annotation-driven provides -->
<bean class="org.springframework.web.servlet.mvc.
annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter" />
<ref bean="marshallingHttpMessageConverter"/>
</list>
</property>
</bean>

<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.
MarshallingHttpMessageConverter">
<property name="marshaller" ref="jaxbMarshaller" />
<property name="unmarshaller" ref="jaxbMarshaller" />
<property name="supportedMediaTypes">
<list>
<!-- This handles browsers like FF who add
the charset to the XHR request. -->
<bean class="org.springframework.http.MediaType">
<constructor-arg index="0" value="application"/>
<constructor-arg index="1" value="xml"/>
<constructor-arg index="2" value="UTF-8"/>
</bean>
</list>
</property>
</bean>

<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.
StringHttpMessageConverter"/>

<oxm:jaxb2-marshaller id="jaxbMarshaller"
contextPath="org.altaregomelb.sync.domain"/>

Given the framework for converting data from HTTP requests already contains the logic to parse out Content-Type from the header, it's a shame that the default beans that are created by the <mvc:annotation-driven/> don't parse the charset properly instead of throwing an exception, given that it is part of the standard to include a charset in XHR requests. All XML/HTTP requests wont be XHR requests it's true, but given the prevalence of AJAX apps out there; the framework should account for it.

References
Spring 3 API