JAX-RS Providers Part I

Providers

They are nothing but implementations of specific JAX-RS interfaces which provide flexibility and extensibility. They either need to be annotated with the @Provider annotation for automatic detection by the JAX-RS container or need to explicitly configured

In this lesson, we are going to cover two providers

  • Message Body Reader: HTTP payload to Java object transformers

  • Message Body Writer: convert Java object into HTTP payloads before sending them to the caller

  • JAX-RS support for JSON-P

Message Body Reader

A MessageBodyReader is an interface whose implementation supports the conversion of HTTP request content to a Java type which can then be consumed by your JAX-RS application.

Default support

Every JAX-RS implementation provides out-of-the-box support for existing data types (i.e. a default Message Body Reader is provided) such as String, primitives, InputStream, Reader, File, byte array (byte[]), JAXB annotated classes, JSON-P objects. The same is applicable for Message Body Writers as well (see the next topic)

It's best to understand this with the help of an example

//Message Body Reader implmentation

@Provider
@Consumes(MediaType.APPLICATION_XML)
public class CustomerDataToCustomerJPAEntity 
    implements MessageBodyReader<CustomerJPA> {


    //implementation for MessageBodyReader interface

    @Override
    public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {

        //return true unconditionally - not ideal. one can include checks
        return true;
    }

    @Override
    public LegacyPOJO readFrom(Class<LegacyPOJO> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, String> mm, InputStream in) throws IOException, WebApplicationException {

        JAXBContext context = null;
        Unmarshaller toJava = null;
        CustomerJAXB jaxbCust = null;
        CustomerJPA jpaCust = null;

        try {
            // unmarshall from XML/JSON to Java object

             context = JAXBContext.newInstance(CustomerJAXB);
             toJava = context.createUnmarshaller();
             jaxbCust = (CustomDomainObj) toJavaObj.unmarshal(in);

             //build JPA entity
                jpaCust = new CustomerJPA(jaxbCust.getUniqueID(), jaxbCust.getName());
        } catch (JAXBException ex) {
            Logger.getLogger(MessageTransformer.class.getName()).log(Level.SEVERE, null, ex);
        }
        return jpaCust;
    }
}
//Message Body Reader in action

@Stateless
@Path("customers")
public class CustomersResource{

    @PersistenceContext
    EntityManager em;

     @POST
     @Consumes(MediaType.APPLICATION_XML)
     //XML payload -> JAXB -> JPA
    public Response create(CustomerJPA cust){
         //create customer in DB
         em.persist(cust);
         //return the ID of the new customer
        return Response.created(cust.getID()).build();
    }
}

What's going on here ?

  • client sends XML payload (in HTTP message body representation)

  • JAX-RS scans the available Message Body Readers and finds our implementation - CustomerDataToCustomerJPAEntity

  • As per our requirement, we first transform the raw payload into an instance of our JAXB annotated model class (CustomerJAXB) and then build an instance of our custom JPA entity (CustomerJPA)

Benefits

  • Separates the business logic from data transformation code - the conversion is transparent to the application

  • One can have different implementations for conversion of different on-wire representations to their Java types and qualify them at runtime by specifying the media type in the @Produces annotation e.g. you can have separate reader implementations for a GZIP and a serialised (binary) representation to convert them to the same Java type

Message Body Writer

Now that we have seen Readers, Message Body Writers are easy to understand - they are the exact opposite i.e. an implementation of a Message Body Writer transforms a Java type to an on-wire format to be returned to the client.

The below example, is the exact opposite (mirror image) of what was demonstrated earlier. This time, we are returning an instance of our custom class without including any transformation logic in our JAX-RS resource classes - it's encapsulated within our MessageBodyWriter implementation

//Message Body Writer implmentation

@Provider
@Produces(MediaType.APPLICATION_XML)
public class CustomerJPAEntityToCustomerData 
    implements MessageBodyWriter<CustomerJPA> {

    @Override
    public boolean isWriteable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
       //return true unconditionally - not ideal. one can include checks
        return true;
    }

    @Override
    public long getSize(CustomerJPA t, Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
         return -1;
    }

    @Override
    public void writeTo(CustomerJPA t, Class<?> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, Object> mm, OutputStream out) 
    throws IOException, WebApplicationException {

        JAXBContext context = null;
        Marshaller toXML = null;
        CustomerJAXB jaxbCust = new CustomerJAXB();

        jaxbCust(t.getId());
        jaxbCust(t.getName());

        try {
            // marshall from XML/JSON to Java object

             context = JAXBContext.newInstance(CustomerJAXB.class);
             toXML = context.createMarshaller();

             //write marshalled content back to client
             toXML.marshal(jaxbCust, out);

        } catch (JAXBException ex) {
            Logger.getLogger(MessageTransformer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}
//Message Body Writer in action

@Stateless
@Path("customers")
public class CustomersResource{

    @PersistenceContext
    EntityManager em;

     @GET
     @Produces(MediaType.APPLICATION_XML)
     @Path("{id}")
     //JPA -> JAXB -> XML payload
    public Response get(@PathParam("id") String custID){
         //search customer in DB
         CustomerJPA found = em.find(CustomerJPA.class,custID);
       return Response.ok(found).build();
    }
}

JAX-RS and JSON-P integration

This section talks about support for JSON-P in JAX-RS 2.0

JSON-P

The JSON Processing API (JSON-P) was introduced in Java EE 7. It provides a standard API to work with JSON data and is quite similar to its XML counterpart - JAXP. JSON-B (JSON Binding) API is in the works for Java EE 8.

Support for JSON-P in JAX-RS 2.0

JAX-RS 2.0 (also a part of Java EE 7) has out-of-the-box support for JSON-P artifacts like JsonObject, JsonArray and JsonStructure i.e. every JAX-RS 2.0 compliant implementation will provide built in Entity Providers for these objects, making it seamless and easy to exchange JSON data in JAX-RS applications

Let's look at a few code samples

//Returning JSON array

@GET
public JsonArray buildJsonArray(){
  return Json.createArrayBuilder().add("jsonp").add("jaxrs").build();
}
//Accepting JSON Object as payload

@POST
public void acceptJsonObject(JsonObject payload){
  System.out.println("the payload -- "+ payload.toString());
}

These are pretty simple examples, but I hope you get the idea....

Few things to be noted

  • No need to write custom MessageBodyReader or MessageBodyWriter implementations. As mentioned previously, the JAX-RS implementation does it for you for free

  • This feature is not the same as being able to use JAXB annotations on POJOs and exchange JSON versions of the payload (by specifying the application/xml media type) - this is also known as JSON binding and it is one of the potential candidates for Java EE 8. I have experimented with this and observed that GlassFish 4.1 (Jersey) and Wildfly 8.x (RESTEasy) support this by default

Last updated