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.
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:
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.
I encountered this problem while upgrading from CXF 2.7.0 to 3.0.2. Here is what I did to resolve it:
Included the following in my pom.xml
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-providers</artifactId> <version>3.0.2</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-jaxrs</artifactId> <version>1.9.0</version> </dependency>
and added the following provider
<jaxrs:providers> <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" /> </jaxrs:providers>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With