IgorShare Thoughts and Ideas

Consulting and Training

XML/JSON symmetric REST web services providers for Jersey

Posted by Igor Moochnick on 07/27/2009

Many times I’ve been asked to provide a set of Web Services interfaces where both JSON and XML clients can communicate with the server. Primarily it’s done for a set of reasons:

  1. XML is very convenient to use for inter-service communication.
  2. JSON is great for AJAX (web) clients. It’s perfect for GWT too.

In the recent project we’ve been using XStream for all serialization aspects and, since it can serialize both to XML and JSON, it was plugged into Jersey as a provider too. Following you can see XML and JSON providers implemented using XStream library.

XML Provider (annotated to be a default provider, it’ll be used if no Content-Type or Accept headers provided):

package com.igorshare.myserver.ws;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.provider.AbstractMessageReaderWriterProvider;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.CompactWriter;

@Produces({ MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.WILDCARD})
@Consumes({ MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.WILDCARD})
@Provider
public class XStreamXmlProvider extends AbstractMessageReaderWriterProvider<Object> {
    private static final Set<Class<?>> processed = new HashSet<Class<?>>();
    private static final XStream xstream = new XStream();
    private static final String DEFAULT_ENCODING = "utf-8";
    
    // Static initializer
    {
        xstream.setMode(XStream.NO_REFERENCES);
        xstream.autodetectAnnotations(true);
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType arg3) {
        return true;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType arg3) {
	return true;
    }

    protected static String getCharsetAsString(MediaType m) {
        if (m == null) {
            return DEFAULT_ENCODING;
        }
        String result = m.getParameters().get("charset");
        return (result == null) ? DEFAULT_ENCODING : result;
    }

    protected XStream getXStream(Class<?> type) {
        synchronized (processed) {
            if (!processed.contains(type)) {
                xstream.processAnnotations(type);
                processed.add(type);
            }
        }
        return xstream;
    } 

    public Object readFrom(Class<Object> aClass, Type genericType, Annotation[] annotations,
            MediaType mediaType, MultivaluedMap<String, String> map, InputStream stream)
            throws IOException, WebApplicationException  {
        String encoding = getCharsetAsString(mediaType);
        XStream xStream = getXStream(aClass);
        return xStream.fromXML(new InputStreamReader(stream, encoding));
    }

    public void writeTo(Object o, Class<?> aClass, Type type, Annotation[] annotations,
            MediaType mediaType, MultivaluedMap<String, Object> map, OutputStream stream)
            throws IOException, WebApplicationException {
        String encoding = getCharsetAsString(mediaType);
        XStream xStream = getXStream(o.getClass());
        xStream.marshal(o, new CompactWriter(new OutputStreamWriter(stream, encoding)));
    }
}

 

JSON Provider (Note: it uses different formats for in-stream and out-stream to simplify the AJAX eval() structure):

package package com.igorshare.myserver.ws;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.provider.AbstractMessageReaderWriterProvider;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JsonWriter;

@Produces({MediaType.APPLICATION_JSON })
@Consumes({MediaType.APPLICATION_JSON })
@Provider
public class XStreamJsonProvider extends AbstractMessageReaderWriterProvider<Object>
{
    private static final Set<Class<?>> processed = new HashSet<Class<?>>();
    private static final XStream xstreamIn = new XStream(new JettisonMappedXmlDriver());
    private static final XStream xstreamOut = new XStream(new JsonHierarchicalStreamDriver() {
	    public HierarchicalStreamWriter createWriter(Writer writer) {
	        return new JsonWriter(writer, new char[0], "", JsonWriter.DROP_ROOT_MODE);
	    }
	});
    private static final String DEFAULT_ENCODING = "utf-8";
    
    // Static Initializer
    {
        xstreamIn.setMode(XStream.NO_REFERENCES);
        xstreamOut.setMode(XStream.NO_REFERENCES);
        xstreamOut.autodetectAnnotations(true);
    }
    
    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType arg3) {
    	return true;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType arg3) {
	return true;
    }

    protected static String getCharsetAsString(MediaType m) {
        if (m == null) {
            return DEFAULT_ENCODING;
        }
        String result = m.getParameters().get("charset");
        return (result == null) ? DEFAULT_ENCODING : result;
    }

    protected XStream getXStreamIn(Class<?> type) {
        synchronized (processed) {
            if (!processed.contains(type)) {
                xstreamIn.processAnnotations(type);
                processed.add(type);
            }
        }
        return xstreamIn;
    }
    
    public Object readFrom(Class<Object> aClass, Type genericType, Annotation[] annotations,
            MediaType mediaType, MultivaluedMap<String, String> map, InputStream stream)
            throws IOException, WebApplicationException {
        String encoding = getCharsetAsString(mediaType);
        XStream xStream = getXStreamIn(aClass);
        return xStream.fromXML(new InputStreamReader(stream, encoding));
    }	

    public void writeTo(Object o, Class<?> aClass, Type type, Annotation[] annotations,
            MediaType mediaType, MultivaluedMap<String, Object> map, OutputStream stream)
            throws IOException, WebApplicationException {
        String encoding = getCharsetAsString(mediaType);
        xstreamOut.toXML(o, new OutputStreamWriter(stream, encoding));
    }
}

 

Note: XStream is rapidly falling out of my favor and  many people I’ve recently talked too because of it’s quirks and a limited control over the serialized structure. Recently I’ve started using JAXB as a primary serialization mechanism. Stay tuned – I’ll post the JAXB examples too.

Reference: I’ve found another reference that covers the same topic – XML Provider on XStream and JSON provider on Json-lib.

Advertisements

2 Responses to “XML/JSON symmetric REST web services providers for Jersey”

  1. NewsPeeps said

    XML/JSON symmetric REST web services providers for Jersey « IgorShare Weblog…

    Thank you for submitting this cool story – Trackback from NewsPeeps…

  2. aravias said

    Hi,
    while trying to use this messagebodyreaderwriter impl for JSON I get this message

    A message body writer for Java type, class com.cerner.edi.address.verification.model.Item2, and MIME media type, application/json, was not found

    at runtime its not getting picked up when I return certain java objects which I want to be converted to JSON format. Is there any other configuration required for the jersey runtime to use this implementation for the objects being returned from a REST service.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: