Monday, March 30, 2009

Digging into Jersey JAX-RS: 2. custom message body writers

I decided to start with something simple, like a service that simply returns a java POJO object.

So with that in mind, I created an EntryPoint that mapped to the root path of /.

Simple Service

@Path("/")
public class EntryPoint {
     @GET
     @Produces(MediaType.APPLICATION_XML)
     public Data getData() {
          final Data data = new Data();
          data.add("key1", "value1");
          data.add("key2", "value2");
          return data;
     }
}

And I tried to access the web application with my Safari, deployed to tomcat automatically with eclipse.

A message body writer for Java type, class me.kentlai.jaxrs.services.Data, and MIME media type, application/xml, was not found.

Hmm.. I wonder what are all the message body writers registered in the system. Other than the com.sun.jersey.server.impl.template.ViewableMessageBodyWriter in jersey-server, the following were found in jersey-core.

com.sun.jersey.core.impl.provider.entity.StringProvider
com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
com.sun.jersey.core.impl.provider.entity.FileProvider
com.sun.jersey.core.impl.provider.entity.InputStreamProvider
com.sun.jersey.core.impl.provider.entity.DataSourceProvider
com.sun.jersey.core.impl.provider.entity.RenderedImageProvider
com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider
com.sun.jersey.core.impl.provider.entity.FormProvider
com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$Text
com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$Text
com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$Text
com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
com.sun.jersey.core.impl.provider.entity.ReaderProvider
com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider
com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter

Ok to be fair, there are various ways to serialize xml. And various xml libraries. There are a few in-built xml providers, but they take in JAXB elements of DOM elements (as far as I can see. I am not an xml guru).

It is a good chance to experiment with my own provider. An xstream writer provider.

Custom XStream Message Body Writer

This is not an entry about how to use xstream. There are much better articles out there for xstream, so I will skim over the details.

I annotated my POJO with xstream annotations, to make the xml output prettier.

The xstream writer provider was rather simple to write. All it had to do was match the xml media type, and write the object out with an xstream instance. I am not using any xstream annotation here yet. Note also that I annotated it with javax.ws.rs.ext.Provider and com.sun.jersey.spi.resource.Singleton. The first allows the class to be picked up when Jersey scans the package for providers, and the second requests jersey to create only a single instance of the writer for the entire web application. This is a better option as readers/writers should be stateless and can be reused.

@Provider
@Singleton
public class XStreamMessageBodyWriter implements MessageBodyWriter<Object> {
     public long getSize(final Object t, final Class<?> type, final Type genericType,
               final Annotation[] annotations, final MediaType mediaType) {
          return -1;
     }
     public boolean isWriteable(final Class<?> type, final Type genericType,
               final Annotation[] annotations, final MediaType mediaType) {
          return mediaType.isCompatible(MediaType.APPLICATION_XML_TYPE) ||
               mediaType.isCompatible(MediaType.TEXT_XML_TYPE);
     }
     public void writeTo(final Object t, final Class<?> type, final Type genericType,
               final Annotation[] annotations, final MediaType mediaType,
               final MultivaluedMap<String, Object> httpHeaders,
               final OutputStream entityStream) throws IOException, WebApplicationException {
          xstream.processAnnotations(type);
          xstream.toXML(t, entityStream);
     }
     private final XStream xstream = new XStream();
}

And accessing the redeployed page gives me the following:

<data>
     <entry key="service1">
          <value>path1</value>
     </entry>
     <entry key="service2">
          <value>path2</value>
     </entry>
</data>

Cool! Now how about json?

Custom JSON Message Body Writer

Now change the javax.ws.rs.Produces to produce MediaType.APPLICATION_JSON instead.

A message body writer for Java type, class me.kentlai.jaxrs.services.Data, and MIME media type, application/json, was not found

That was expected. Now, to handle Json specifically with a JSON message body writer using json-lib.

@Provider
@Singleton
public class JSONMessageBodyWriter implements MessageBodyWriter<Object> {
     public long getSize(final Object t, final Class<?> type, final Type genericType,
               final Annotation[] annotations, final MediaType mediaType) {
          return -1;
     }
     public boolean isWriteable(final Class<?> type, final Type genericType,
               final Annotation[] annotations, final MediaType mediaType) {
          return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
     }
     public void writeTo(final Object t, final Class<?> type, final Type genericType,
               final Annotation[] annotations, final MediaType mediaType,
               final MultivaluedMap<String, Object> httpHeaders,
               final OutputStream entityStream) throws IOException, WebApplicationException {
          entityStream.write(serializer.toJSON(t).toString().getBytes());
     }
     private final JSONSerializer serializer = new JSONSerializer();
}

Accessing the page gives me the following:

{"data":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}]}

End of this post

This is a simple post, with nothing overly hard to try nor implement.

Next up I will probably try to trace the flow of request through the components up to the response.

Update: My bad, I did not notice that if I annotated my POJO with a JAXB @XmlRootEntity/Type, it will trigger the JAXB writer.

blog comments powered by Disqus