support both Protobuf and JSON with Jersey Restful web service

In one of my projects, I developed the Restful web service on Jersey and consume/produces Protobuf message. It all worked well until a new requirement came to add support of Javascript client.  So, I added 4 more classes:

HextUtils

JsonFormat

JsonMessageReader

JsonMessageWriter

along with two existing classes

ProtobufMessageReader

ProtobufMessageWriter

now the web service can support both message formats. Just make sure to NOT have any media type explicitly defined in the  web service classes – Jersey will pick up the correct reader/writer based on the incoming contest-type and accept headers.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.GeneratedMessage;

@Provider
@Consumes(“application/json”)
public class JsonMessageReader implements MessageBodyReader {
public static String LS = System.getProperty(“line.separator”);
private ExtensionRegistry extensionRegistry = ExtensionRegistry
.newInstance();
@Override
public boolean isReadable(Class arg0, Type arg1, Annotation[] arg2,
MediaType mediaType) {
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}

public T readFrom(Class type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException {
try
{
Method newBuilder = type.getMethod(“newBuilder”);

GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(type);
String data = convertInputStreamToString(entityStream);
JsonFormat.merge(data, extensionRegistry, builder);
return (T) builder.build();
}
catch (Exception e)
{
throw new WebApplicationException(e);
}
}

private String convertInputStreamToString(InputStream io) {
StringBuffer sb = new StringBuffer();
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(io));
String line = reader.readLine();
while (line != null) {
sb.append(line).append(LS);
line = reader.readLine();
}
} catch (IOException e) {
throw new RuntimeException(“Unable to obtain an InputStream”, e);

}
return sb.toString();
}
}


import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

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.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import com.google.protobuf.Message;
@Provider
@Produces(“application/json”)
public class JsonMessageWriter implements MessageBodyWriter {

@Override
public long getSize(T arg0, Class arg1, Type arg2, Annotation[] arg3,
MediaType arg4) {
return -1;
}

@Override
public boolean isWriteable(Class arg0, Type arg1, Annotation[] arg2,
MediaType mediaType) {
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}

@Override
public void writeTo(T arg0, Class arg1, Type arg2, Annotation[] arg3,
MediaType arg4, MultivaluedMap arg5,
OutputStream outputStream) throws IOException, WebApplicationException {
outputStream.write(JsonFormat.printToString((Message) arg0).getBytes());

}

}


import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message;

/**
* Message Body Reader.
*/
@Provider
@Consumes(“application/x-protobuf”)
public class ProtobufMessageReader implements MessageBodyReader
{
public boolean isReadable(Class type, Type genericType,
Annotation[] annotations, MediaType mediaType)
{
return Message.class.isAssignableFrom(type);
}

public Message readFrom(Class type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException
{
try
{
Method newBuilder = type.getMethod(“newBuilder”);

GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(type);

return builder.mergeFrom(entityStream).build();
}
catch (Exception e)
{
throw new WebApplicationException(e);
}
}
}

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

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.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import com.google.protobuf.Message;

/**
* A Message Body Writer
*/
@Provider
@Produces(“application/x-protobuf”)
public class ProtobufMessageWriter implements MessageBodyWriter {
public boolean isWriteable(Class type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
return Message.class.isAssignableFrom(type);
}

public long getSize(Message m, Class type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
return m.getSerializedSize();
}

public void writeTo(Message m, Class type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException,
WebApplicationException {
entityStream.write(m.toByteArray());
}
}

All credits go to the people who have contributed and open-sourced the protobuf and json projects.

Advertisements

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