Friday, October 23, 2009

XStream and Spring 3.0

I have spent my last three weeks happily using Spring 3 rc1 getting to know some new features and using some old features that I have not had the opportunity to use before. Specifically I enjoyed the greatly reduced xml used when using annotations. Also, the REST handling in the controllers is worth the move.

However, I ran into a bit of problem when trying to use Spring's ContentNegotiatingViewResolver to return XML. I quickly got Spring wired to use a Marshalling view and I chose the XStreamMarshaller. Everything was going great. Development was quick and easy. I had JSON and XML responses going quickly (I did find a bug with declaring the defaultContentType, but they say they will fix this asap). I was able to focus on the business problem I was trying to solve instead of getting bogged down in framework muck.

Then I noticed a slight problem with my results in XML, there was no XML declaration in the response ( was missing). Going over the documentation lead me to the fact that the MarshallingView assumes that the Marshaller knows how to write the response document, but in this case, the Marshaller is an XStream implementation, and the XStream guys explicitly say "this is not my problem".

Seems like a case of the hot potato being passed back and forth. I didn't find anything in the documentation or forums on how to fix this. It feels like this should just be a boolean or String property to be set on the XStreamMarshaller which Spring provides, but it is not (maybe in 3.1?).

So I created a hack to fix this, I extended XStreamMarshaller and over wrote the following method:

/**
* {@inheritDoc}
*/
@Override
protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
writer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
super.marshalWriter(graph, writer);
}

My config file looks like this:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg ref="xstream" />
</bean>
</list>
</property>
<property name="defaultContentType" ref="jsonMediaType" />
<property name="ignoreAcceptHeader" value="true" />
</bean>

<bean id="xstream" class="some.path.to.your.class.which.extends.XStreamMarshaller" >
<property name="autodetectAnnotations" value="true" />
<property name="converters">
<array>
<bean id="someConverter" class="some.path.to.your.class.which.implementds.Converter" />
</array>
</property>
</bean>

It feels hacky, and I hate the solution, but I couldn't find another way to accomplish this task. Am I totally missing something here? Is there a simpler solution? Anyway, I hope this helps someone out down the line.

2 comments:

  1. Jon:
    Do you in fact know that the response will be serialized in UTF-8?
    If you don't, it's a bad idea to specify UTF-8 in the XML declaration, since a mismatch is a fatal error.
    If you do, you don't need to specify it, since both the XML declaration and the encoding declaration within it are optional, and the default is UTF-8 (see http://www.w3.org/TR/REC-xml/#NT-XMLDecl).

    Jim

    ReplyDelete
  2. Jim, you are right, I do not want to include the encoding here. I am sending the encoding with the headers, and should not have declared it here. I will make an edit here for that change.

    Thanks for the heads up.

    ReplyDelete