JAX-RS Entity Providers

RESTful

JAX-RS Entity Providers


1. Introduction

The JAX-RS supports request and response message of common Java types by default. If it is required to use custom Java types, the JAX-RS gives the possibility to do it by adding custom entity provider. The JAX-RS components that provides this possibility have several names: entity provider, message body provider, message body worker or message body writer and reader

A custom entity provider class should implement the MessageBodyReader<T> or the MessageBodyWriter<T> interface, or both. And be annotated with @Provider annotation.

2. MessageBodyWriter

A MessageBodyWriter<T> converts a message payload from a specific Java type into a stream.

public interface MessageBodyWriter<T extends Object> {

    public boolean isWriteable(Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType);

    public long getSize(T t,
        Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType);

    public void writeTo(T t,
        Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType,
        MultivaluedMap<String, Object> httpHeaders,
        OutputStream entityStream) throws IOException, WebApplicationException;
}

2.1. MessageBodyWriter.isWriteable method

A method isWriteable should return true if the MessageBodyWriter<T> can write the T type. The isWriteable method can be executed many times by Jersey runtime to verify whether the provider can process a particular combination of media type, entity Java type and attached annotations. Define @Produces annotation on the MessageBodyWriter class to limit the execution number. 

Lets view the meaning of the method parameters:

  • The type defines a raw Java type of the entity (for instance, a java.util.Set class).
  • The genericType defines ParameterizedType of the entity (for instance, a java.util.Set<String> class).
  • The annotations parameter contains annotations attached to the resource method or to the entity by building response.
  • The mediaType is the media type attached to the response entity by annotating the resource method with a @Produces annotation. 

2.2. MessageBodyWriter.writeTo method

The writeTo method is called after a message body writer is chosen as the most appropriate. The method has parameters with the same meaning as isWriteable method. Additional parameters are:

  • The httpHeader contains HTTP headers associated with the outbound message.
  • The first parameter T contains the entity instance which should be serialized.
  • The entityStream contains the output stream where the entity should be serialized. The entityStream shouldn't be closed at the end of method manually - it will be done by Jersey.

2.3. MessageBodyWriter.getSize method

The getSize method can return the entity size which will be used in "Content-Length" response header. If content length cannot be calculated, the method should return "-1".

2.4. Example of MessageBodyWriter

Let's view the example of implementing MessageBodyWriter interface. Below is the root resource, which get method returns custom java type Exam:

Example 2.1 Resource class

@Path("exams")
public class ExamService {
    Map<Integer, Exam> exams;

    public ExamService() {
        exams = new HashMap<Integer, Exam>();
        exams.put(1, new Exam(1, "OCEJPAD 6"));
        exams.put(2, new Exam(2, "OCEJWSD 6"));
    }

    @GET
    @Path("{id}")
    public Exam get(@PathParam("id") int id) {
        return exams.get(id);
    }

    @POST
    public String post(Exam exam) {
        exams.put(exam.getId(), exam);
        return exams.toString();
    }
}

Example 2.2 Exam enity class

Exam class is defined in the next example: 

public class Exam implements Serializable {
    private int id;
    private String name;

    public Exam(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Exam{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Example 2.3 MessageBodyWriter implementation

And finally custom MessageBodyWriter: 

@Provider
public class ExamMessageBodyWriter implements MessageBodyWriter<Exam> {
    public boolean isWriteable(Class<?> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType) {
        return type == Exam.class;
    }

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

    public void writeTo(Exam exam,
                        Class<?> type,
                        Type genericType,
                        Annotation[] annotations,
                        MediaType mediaType,
                        MultivaluedMap<String, Object> httpHeaders,
                        OutputStream entityStream)
            throws IOException, WebApplicationException {
        ObjectOutputStream out = new ObjectOutputStream(entityStream);
        out.writeObject(exam);
        out.close();
    }
}

2.5. Testing a MessageBodyWriter

Let's send a request to the created resource:

Example 2.4 Client to test ExamMessageBodyWriter

Client client = Client.create();
WebResource r = client.resource(".../exams/2");
String response = r.get(String.class);
System.out.println(response);

The result on the console will be something like:

com.examclouds.example1.Exam: id name Ljava/lang/String; OCEJWSD 6

The received answer doesn't correspond to the result of Exam.toString() method. To change it we need to implement MessageBodyReader on the client side, what will be done in the example 3.2.

3. MessageBodyReader

A MessageBodyReader<T> gives the possibility to read the message body representation from an input stream and convert the data into an instance of a specific Java type.

public interface MessageBodyReader<T extends Object> {

    public boolean isReadable(Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType);

    public T readFrom(Class<T> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType,
        MultivaluedMap<String, String> httpHeaders,
        InputStream entityStream) throws IOException, WebApplicationException;
}

3.1. MessageBodyReader.isReadable method

The method returns true if the MessageBodyReader can deserialize the given type. The parameters of the method are similar to parameters of MessageBodyWriter.isWritable. The only difference is that mediaType parameter is the media type attached to the response entity by annotating the resource method with a @Consumes annotation.

In order to reduce the number of isReadable executions, define the consumable media types with the @Consumes annotation on the MessageBodyReader.

3.2. MessageBodyReader.readFrom method

The readForm() method has parameters with the same meaning as isReadable(). The entityStream parameter represents the entity input stream from which the entity should be read and deserialized into a Java entity. The entity input stream shouldn't be closed manually in MessageBodyReader implementation, because it will be automatically done by Jersey runtime. 

3.3. Example of MessageBodyReader

To deserialize the entity of Exam on the server or the client side, it is required to implement a custom MessageBodyReader:

Example 3.1 MessageBodyReader implementation

public class ExamMessageBodyReader implements MessageBodyReader<Exam> {
    public boolean isReadable(Class<?> type,
                              Type genericType,
                              Annotation[] annotations,
                              MediaType mediaType) {
        return type == Exam.class;
    }

    public Exam readFrom(Class<Exam> type,
                         Type genericType,
                         Annotation[] annotations,
                         MediaType mediaType,
                         MultivaluedMap<String, String> httpHeaders,
                         InputStream entityStream) throws IOException, WebApplicationException {
        Exam exam = null;
        try {
            ObjectInputStream in = new ObjectInputStream(entityStream);
            exam = (Exam) in.readObject();
            in.close();
        } catch (ClassNotFoundException e) {
            System.out.print("Class not found");
        }
        return exam;
    }
}

Example 3.2 Register ExamMessageBodyReader with Client API

The MessageBodyReader<T> and MessageBodyWriter<T> can be registered in a configuration of Jersey Client API components without any change in the code.

The code in the example is similar to Example 2.4, the difference is that the ExamMessageBodyReader is registered in client configuration. This gives the possibility to receive in the response Exam object instead of String:

ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getClasses().add(ExamMessageBodyReader.class);
Client client = Client.create(clientConfig);
WebResource r = client.resource(".../exams/2");
Exam response = r.get(Exam.class);
System.out.println(response);

The result is:

Exam{id=2, name='OCEJWSD 6'}

Example 3.3 Register ExamMessageBodyWriter with Client API

This example shows how to post Exam object to the resource. To have the possibility serialize Exam object, we need to register ExamMessageBodyWriter with client API. Corresponding ExamMessageBodyReader should be registered on the server side.

Exam exam = new Exam(3, "OCJP 6");
ClientConfig clientConfig = new DefaultClientConfig();
Set<Class<?>> classes = clientConfig.getClasses();
classes.add(ExamMessageBodyWriter.class);
Client client = Client.create(clientConfig);
WebResource r = client.resource(".../exams");
String response = r.post(String.class, exam);
System.out.println(response);

The result is:

{1=Exam{id=1, name='OCEJPAD 6'}, 2=Exam{id=2, name='OCEJWSD 6'}, 3=Exam{id=3, name='OCJP 6'}}

 

Sources: Chapter 8. JAX-RS Entity Providers, Implement a MessageBodyReader and MessageBodyWriter



0 comments
Leave your comment: