March 11, 2015

JAX-RS Message Body Writer & Reader using Kryo serialization framework


In this post I'm gonna show you how simple it is to enhance JAX-RS application by a new body format. In this example I use Kryo serialization framework.

Kryo serialization framework


Kryo is a fast and efficient object graph serialization framework for Java. The goals of the project are speed, efficiency, and an easy to use API. The project is useful any time objects need to be persisted, whether to a file, database, or over the network.
Kryo can also perform automatic deep and shallow copying/cloning. This is direct copying from object to object, not object->bytes->object.

JAX-RS Message Body providers

JAX-RS allows you to enhance JAX-RS engine to support another body type. There is MessageBodyReader and MessageBodyWriter SPI to be implemented. I have chosen to use application/x-kryo media type for Kryo (de)serialized message bodies.


package cz.kramolis.jersey.kryo;

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

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

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

@Consumes("application/x-kryo")
@Produces("application/x-kryo")
public class KryoMessageBodyProvider implements
                        MessageBodyWriter<Object>,
                        MessageBodyReader<Object> {

    //
    // MessageBodyWriter
    //

    @Override
    public long getSize(Object object, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

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

    @Override
    public void writeTo(Object object, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        final Kryo kryo = new Kryo();
        final Output output = new Output(baos);

        kryo.writeObject(output, object);
        output.close();

        entityStream.write(baos.toByteArray());
    }

    //
    // MessageBodyReader
    //

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

    @Override
    public Object readFrom(Class<Object> type, Type genericType,
                           Annotation[] annotations, MediaType mediaType,
                           MultivaluedMap<String, String> httpHeaders,
                           InputStream entityStream) throws IOException, WebApplicationException {
        try {
            Kryo kryo = new Kryo();
            Input input = new Input(entityStream);
            Object object = kryo.readObject(input, type);
            input.close();

            return object;
        } catch (Exception e) {
            throw new WebApplicationException(e);
        }
    }

}

You can see it is really simple Message Body provider. But I hope it shows you the way...

Than you need to register provider class into JAX-RS Application, e.g.


package cz.kramolis.jersey.kryo;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.core.Application;

public class JaxRsApplication extends Application {

    static final Set<Class<?>> APP_CLASSES = new HashSet<Class<?>>() {{
        add(KryoMessageBodyProvider.class);
        add(PersonResource.class);
    }};

    @Override
    public Set<Class<?>> getClasses() {
        return APP_CLASSES;
    }

}


And finally usage of the new body type is very easy:


package cz.kramolis.jersey.kryo;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/")
@Consumes("application/x-kryo")
@Produces("application/x-kryo")
public class PersonResource {

    @POST
    public Person echo(final Person person) {
        return person;
    }

    @PUT
    public void put(final Person person) {
    }

    @GET
    public Person get() {
        return new Person("Wolfgang", 21, "Salzburg");
    }

}


Conclusion

Whole sources are available in dedicated GitHub repository. Use it as you need. JAX-RS provides really simple way how to extend it's support. And Kryo is really powerful framework. Give it a try. Look at deeper JVM serializers comparision.

In one of the next blog posts I will provider some performance data comparing Kryo with JSON, XML and others message body providers.

Links