CXF: No message body writer found for class - automatically mapping non-simple resources

javamonkey79 picture javamonkey79 · Jun 10, 2011 · Viewed 109k times · Source

I am using the CXF rest client which works well for simple data types (eg: Strings, ints). However, when I attempt to use custom Objects I get this:

Exception in thread "main" org.apache.cxf.interceptor.Fault: .No message body writer found for class : class com.company.datatype.normal.MyObject.
    at org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.handleMessage(ClientProxyImpl.java:523)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:438)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl.invoke(ClientProxyImpl.java:177)
    at $Proxy13.execute(Unknown Source)
    at com.company.JaxTestClient.main(JaxTestClient.java:26)
Caused by: org.apache.cxf.jaxrs.client.ClientWebApplicationException: .No message body writer found for class : class com.company.datatype.normal.MyObject.
    at org.apache.cxf.jaxrs.client.AbstractClient.reportMessageHandlerProblem(AbstractClient.java:491)
    at org.apache.cxf.jaxrs.client.AbstractClient.writeBody(AbstractClient.java:401)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.handleMessage(ClientProxyImpl.java:515)
    ... 5 more

I'm calling it like this:

JaxExample jaxExample = JAXRSClientFactory.create( "http://localhost:8111/", JaxExample.class );
MyObject before = ...
MyObject after = jaxExample.execute( before );

Here is the method in the interface:

@POST
@Path( "execute" )
@Produces( "application/json" )
MyObject execute( MyObject myObject );

The restlet library does this quite simply, by adding the XStream dependency to your path it "just works". Does CXF something similar?

EDIT #1:

I've posted this as a feature improvement to the CXF issue management system here. I can only hope this will get attended to.

Answer

philwb picture philwb · Jun 14, 2011

It isn't quite out of the box but CXF does support JSON bindings to rest services. See cxf jax-rs json docs here. You'll still need to do some minimal configuration to have the provider available and you need to be familiar with jettison if you want to have more control over how the JSON is formed.

EDIT: Per comment request, here is some code. I don't have a lot of experience with this but the following code worked as an example in a quick test system.

//TestApi parts
@GET
@Path ( "test" )
@Produces ( "application/json" )
public Demo getDemo () {
    Demo d = new Demo ();
    d.id = 1;
    d.name = "test";
    return d;
}

//client config for a TestApi interface
List providers = new ArrayList ();
JSONProvider jsonProvider = new JSONProvider ();
Map<String, String> map = new HashMap<String, String> ();
map.put ( "http://www.myserviceapi.com", "myapi" );
jsonProvider.setNamespaceMap ( map );
providers.add ( jsonProvider );
TestApi proxy = JAXRSClientFactory.create ( url, TestApi.class, 
    providers, true );

Demo d = proxy.getDemo ();
if ( d != null ) {
    System.out.println ( d.id + ":" + d.name );
}

//the Demo class
@XmlRootElement ( name = "demo", namespace = "http://www.myserviceapi.com" )
@XmlType ( name = "demo", namespace = "http://www.myserviceapi.com", 
    propOrder = { "name", "id" } )
@XmlAccessorType ( XmlAccessType.FIELD )
public class Demo {

    public String name;
    public int id;
}

Notes:

  1. The providers list is where you code configure the JSON provider on the client. In particular, you see the namespace mapping. This needs to match what is on your server side configuration. I don't know much about Jettison options so I'm not much help on manipulating all of the various knobs for controlling the marshalling process.
  2. Jettison in CXF works by marshalling XML from a JAXB provider into JSON. So you have to ensure that the payload objects are all marked up (or otherwise configured) to marshall as application/xml before you can have them marshall as JSON. If you know of a way around this (other than writing your own message body writer), I'd love to hear about it.
  3. I use spring on the server so my configuration there is all xml stuff. Essentially, you need to go through the same process to add the JSONProvider to the service with the same namespace configuration. Don't have code for that handy but I imagine it will mirror the client side fairly well.

This is a bit dirty as an example but will hopefully get you going.

Edit2: An example of a message body writer that is based on xstream to avoid jaxb.

@Produces ( "application/json" )
@Consumes ( "application/json" )
@Provider
public class XstreamJsonProvider implements MessageBodyReader<Object>,
    MessageBodyWriter<Object> {

@Override
public boolean isWriteable ( Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType ) {
    return MediaType.APPLICATION_JSON_TYPE.equals ( mediaType ) 
        && type.equals ( Demo.class );
}

@Override
public long getSize ( Object t, Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType ) {
    // I'm being lazy - should compute the actual size
    return -1;
}

@Override
public void writeTo ( Object t, Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType, 
    MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) 
    throws IOException, WebApplicationException {
    // deal with thread safe use of xstream, etc.
    XStream xstream = new XStream ( new JettisonMappedXmlDriver () );
    xstream.setMode ( XStream.NO_REFERENCES );
    // add safer encoding, error handling, etc.
    xstream.toXML ( t, entityStream );
}

@Override
public boolean isReadable ( Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType ) {
    return MediaType.APPLICATION_JSON_TYPE.equals ( mediaType ) 
        && type.equals ( Demo.class );
}

@Override
public Object readFrom ( Class<Object> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType, 
    MultivaluedMap<String, String> httpHeaders, InputStream entityStream ) 
    throws IOException, WebApplicationException {
    // add error handling, etc.
    XStream xstream = new XStream ( new JettisonMappedXmlDriver () );
    return xstream.fromXML ( entityStream );
}
}

//now your client just needs this
List providers = new ArrayList ();
XstreamJsonProvider jsonProvider = new XstreamJsonProvider ();
providers.add ( jsonProvider );
TestApi proxy = JAXRSClientFactory.create ( url, TestApi.class, 
    providers, true );

Demo d = proxy.getDemo ();
if ( d != null ) {
    System.out.println ( d.id + ":" + d.name );
}

The sample code is missing the parts for robust media type support, error handling, thread safety, etc. But, it ought to get you around the jaxb issue with minimal code.

EDIT 3 - sample server side configuration As I said before, my server side is spring configured. Here is a sample configuration that works to wire in the provider:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
    http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml" />

<jaxrs:server id="TestApi">
    <jaxrs:serviceBeans>
        <ref bean="testApi" />
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <bean id="xstreamJsonProvider" class="webtests.rest.XstreamJsonProvider" />
    </jaxrs:providers>
</jaxrs:server>

<bean id="testApi" class="webtests.rest.TestApi">
</bean>

</beans>

I have also noted that in the latest rev of cxf that I'm using there is a difference in the media types, so the example above on the xstream message body reader/writer needs a quick modification where isWritable/isReadable change to:

return MediaType.APPLICATION_JSON_TYPE.getType ().equals ( mediaType.getType () )
    && MediaType.APPLICATION_JSON_TYPE.getSubtype ().equals ( mediaType.getSubtype () )
    && type.equals ( Demo.class );

EDIT 4 - non-spring configuration Using your servlet container of choice, configure

org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet

with at least 2 init params of:

jaxrs.serviceClasses
jaxrs.providers

where the serviceClasses is a space separated list of the service implementations you want bound, such as the TestApi mentioned above and the providers is a space separated list of message body providers, such as the XstreamJsonProvider mentioned above. In tomcat you might add the following to web.xml:

<servlet>
    <servlet-name>cxfservlet</servlet-name>
    <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
    <init-param>
        <param-name>jaxrs.serviceClasses</param-name>
        <param-value>webtests.rest.TestApi</param-value>
    </init-param>
    <init-param>
        <param-name>jaxrs.providers</param-name>
        <param-value>webtests.rest.XstreamJsonProvider</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

That is pretty much the quickest way to run it without spring. If you are not using a servlet container, you would need to configure the JAXRSServerFactoryBean.setProviders with an instance of XstreamJsonProvider and set the service implementation via the JAXRSServerFactoryBean.setResourceProvider method. Check the CXFNonSpringJaxrsServlet.init method to see how they do it when setup in a servlet container.

That ought to get you going no matter your scenario.