Providers Sample Application Guide

Version 1.0.0

Overview

The Providers Sample demonstrates how to create custom MessageBodyReaders and MessageBodyWriters to support additional media types not supported by default by the IBM JAX-RS runtime.

Before looking at this sample specifically, we'll take a look at the API which allow the JAX-RS runtime to be extended to support various media types.

We'll then examine the components of the Providers Sample to demonstrate how to extend the JAX-RS runtime in this manner.

JAX-RS Entity Providers

In JAX-RS, entity providers provide mappings between represenations and their Java types. When a request is made to a resource implementation, entity providers are used to marshall/unmarshall the message body to and from the corresponding Java type.

There are two type of providers, MessageBodyReaders and MessageBodyWriters. Both interfaces can be implemented to create custom providers for any media type. Extending the runtime in this manner is the focus of this sample.

Implementations implement the MessgeBodyReader and/or MessageBodyWriter interfaces and are annotatd with the @Provider annotation.

javax.ws.rs.ext.MessageBodyReader<T>

MessageBodyReader implentations are used to unmarshall represenations to a Java type. The following methods define the interface:

boolean isReadable(java.lang.Class type, 
		java.lang.reflect.Type genericType,
		java.lang.annotation.Annotation[] annotations,
		MediaType mediaType);
			
T readFrom(java.lang.Class type,
		java.lang.reflect.Type genericType,
		java.lang.annotation.Annotation[] annotations,
		MediaType mediaType,
		MultivaluedMap httpHeaders,
		java.io.InputStream entityStream);
			

The isReadable method determines if the reader is able to process the message body.

The readFrom method does the work to convert the message body to the corresponding Java type T via the provided InputStream.

javax.ws.rs.ext.MessageBodyWriter<T>

MessageBodyWriter implementations are used to marshall Java Objects to a representation which is put on the response message body. The following methods define the interface:

getSize(T t, java.lang.Class type,
		java.lang.reflect.Type genericType,
		java.lang.annotation.Annotation[] annotations,
		MediaType mediaType);
			
isWriteable(java.lang.Class type,
		java.lang.reflect.Type genericType,
		java.lang.annotation.Annotation[] annotations,
		MediaType mediaType);
		
writeTo(T t, java.lang.Class type,
		java.lang.reflect.Type genericType,
		java.lang.annotation.Annotation[] annotations,
		MediaType mediaType,
		MultivaluedMap httpHeaders,
		java.io.OutputStream entityStream);
			

The getSize method determines the number of bytes to be written to the provided OutputStream in the writeTo method.

The isWriteable method determines whether or not the writer is able to process the Java type and convert an instance to the corresponding representation.

The writeTo method does the work to convert the Java Object of type T to the representation that is put on the response message body via the provided OutputStream.

MessageBodyReader/MessageBodyWriter Annotations

As mentioned earlier, entity provider implementations must be annotated with the @Provider annotation. Additionally the @Consumes and @Produces annotations can be used to explicitly declare a supported media type(s) and to more efficiently determine if a provider implementation should be used.

MessageBodyReaders annotated with the @Consumes annotation will allow the runtime to quickly eliminate candidate providers based on the annotation value. For example, if the annotation value on ReaderA is "text/xml", @Consumes(value="text/xml"), and the Content-Type header value of an incoming request is "text/plain," the runtime can immediately determine that ReaderA is not a candidate to unmarshall the message body on the request.

Likewise, the @Produces annotation can be used to narrow the list of candidate MessageBodyWriters that can be used to marshall Java Objects for an outgoing response.

Declaring Custom Entity Providers to the Runtime

Before the runtime will process custom entity providers, they must be returned by an instance of javax.ws.rs.core.Application. The Application class returns resource and provider classes and instances for use within applications.

Provider Process Flow

Lastly, before we look at the Provider Sample implementations it helps to understand the process flow that takes place when a request is received and the repsonse is sent out, and where the entity providers are invoked in that process.

  1. A request is received from a client application.
  2. The JAX-RS runtime determines the resource class and method to invoke.
  3. A MessageBodyReader is selected based on the @Consumes annotation of the entity provider and resource method if present and the isReadable method.
  4. The readFrom method is invoked on the selected MessageBodyReader.
  5. The resource method is invoked.
  6. A MessageBodyWriter is selected based on the @Produces annotation of the entity provider and resource method if present and the isWritable method
  7. The writeTo method is invoked on the selected MessageBodyWriter.
  8. The response is sent out.

Providers Sample - integer/text, integer/int

In the Providers Sample, the runtime is being extended to support the intger/text and integer/int media types. These are not standard media types.

Providers Sample - IntegerResource.java

IntegerResource is a simple resource class that wraps an Integer Object. The root path for the resource class is /integer.

@Path(value="/integer")
public class IntegerResource {
...
...
}

There are two primary resource methods to note. getInteger and setInteger are the GET and POST methods and produce/accept both the integer/text and integer/int media types.

@POST
@Consumes(value={"integer/int", "integer/text"})
public void setInteger(Integer integer) {
	IntegerResource.integer = integer;
}

@GET
@Produces(value={"integer/int", "integer/text"})
public Response getInteger() {
	return Response.ok(IntegerResource.integer).build();
}

Providers Sample - IntegerTextProvider.java

IntegerTextProvider implements both the MessageBodyReader and MessageBodyWriter interfaces. It consumes and produces the integer/text media type. By implementing the reader and writer interfaces, the integer/text media type is now supported.

@Provider
@Consumes(value="integer/text")
@Produces(value="integer/text")
public class IntegerTextProvider implements MessageBodyReader<Integer>, MessageBodyWriter<Integer> {
...
...
}

The isReadable method is implemented so that true is returned for the Integer type.

public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
	return type == Integer.class;
}

When an @POST request is made to the IntegerResource.setInteger(Integer) method, the runtime must decide which MessageBodyReader will be used to unmarshall the Integer from the request body. Since IntegerTextProvider and IntegerIntProvider both implement the MessageBodyReader interface, and both have isReadable methods that return true for the Integer type, the Content-Type is used to determine that IntegerTextProvider should be used for the integer/text media type.

The readFrom method is implemented to read the body and convert it to an Integer which is then passed to the setInteger method of the IntegerResource class.

public Integer readFrom(Class clazz, Type genericType, Annotation[] annotations, MediaType m, 
		MultivaluedMap headers, InputStream is) throws IOException {
	System.out.println("Reading Integer as text.");
	return Integer.parseInt(toString(is));
}

protected String toString(InputStream is) throws IOException {
	int avail = is.available();

	StringBuilder buf = new StringBuilder();
	final byte[] buffer = new byte[avail];
	int n = 0;
	n = is.read(buffer);
	while (-1 != n) {
		buf.append(new String(buffer, 0, n, UTF8_CHARSET.name()));
		n = is.read(buffer);
	}
	is.close();
	return buf.toString();
}

Like isReadable, isWritable is implemented so that true is returned for the Integer type.

public boolean isWriteable(Class type, Type genericType, Annotation[] annotations,
            MediaType mediaType) {
	return type == Integer.class;
}

When an @GET request is made to the IntegerResource.getInteger() method, the runtime must decide which MessageBodyWriter will be used to marshall the Integer for the response. Since IntegerTextProvider and IntegerIntProvider both implement the MessageBodyWriter interface, and both have isWritable methods that return true for the Integer type, the Accpet header is used to determine that IntegerTextProvider should be used for the integer/text media type.

NOTE: If no Content-Type is specified on the request for a POST or no Accept is specified on the request for a GET, or if the media type is ambiguous (ex: */*), the runtime may choose any supporting implementation based on the isReadable and isWritable return values.

The writeTo method is implemented to convert an Integer to a text String.

public void writeTo(Integer obj, Class clazz, Type genericType, Annotation[] annotations,  
		MediaType m, MultivaluedMap<String, Object> headers, OutputStream os) throws IOException {
	System.out.println("Writing Integer as text.");
        
	List<Object> list = new ArrayList<Object>();
	list.add(m.toString());
	headers.put("Content-Type", list);
        
	String s = obj.toString();
	byte[] b = new byte[s.length()];
	char[] c = s.toCharArray();
	for(int i = 0; i < b.length; ++i)
		b[i] = (byte)c[i];
	os.write(b);
	os.close();
}

Providers Sample - IntegerIntProvider.java

IntegerIntProvider is very similar to IntegerTextProvider except that it produces and consumes the integer/int media type. Rather than converting Strings to Integer and vice versa, the provider converts Integers to and from their binary representations. Implementing the reader and writer interfaces in this manner adds support for the integer/int media type.

@Provider
@Consumes(value="integer/int")
@Produces(value="integer/int")
public class IntegerIntProvider implements MessageBodyReader<Integer>, MessageBodyWriter<Integer> {
...
...
}

Providers Sample - ProvidersApplication.java

The ProvidersApplication class declares the sample resource and provider classes via the getClasses() and getSingletons() methods. In this case, the IntegerResource class is returned in getClasses() meaning it's scope is limited to single requests. The custom entity providers are returned via the getSingletons() method meaning a single instance is used to unmarshall/marshall data for all requests.

@Override
public Set<Class<?>> getClasses() {
    Set<Class<?>> clazzes = new HashSet<Class<?>>();
    clazzes.add(IntegerResource.class);
    return clazzes;
}

@Override
public Set<Object> getSingletons() {
    Set<Object> singletons = new HashSet<Object>();
    singletons.add(new IntegerIntProvider());
    singletons.add(new IntegerTextProvider());
    return singletons;
}