Saturday, August 7, 2010

A MessageBodyWriter isWriteable method for a collection example

I can’t believe how much time I’ve spent today on providing an isWritable method for my JAX-RS MessageBodyWriter class. I just couldn't find a good example out there that did what I wanted to do. Maybe Google search wasn't behaving today!

My goal was to have a provider that translates a List<GPSTrackerCollection> object into an application/xml output stream. GPSTrackerCollection is my own class.

The tricky thing was learning how to test for the presence of a List of GPSTrackerCollection objects within my provider.

First things first, I needed to wrap my collection in a GenericEntity object in order to preserve information on the parameterised type; the runtime strips this information of course (this is known as erasure). Here's an example service implementation from my code base:

@GET
@Path("/history")
public Response getHistory() {
List<GPSTrackerCollection> list = new ArrayList<GPSTrackerCollection>();
GenericEntity<List<GPSTrackerCollection>> entity =
new GenericEntity<List<GPSTrackerCollection>>(list) {};
return Response.ok(entity).build();
}

My MessageBodyWriter ends up looking something like the following:

@Produces("application/xml")
@Provider
public class GPSTrackerCollectionProvider implements
MessageBodyWriter<List<GPSTrackerCollection>> {

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

@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] arg2, MediaType arg3) {

// Ensure that we're handling only List<GPSTrackerCollection> objects.
boolean isWritable;
if (List.class.isAssignableFrom(type)
&& genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] actualTypeArgs = (parameterizedType.getActualTypeArguments());
isWritable = (actualTypeArgs.length == 1 && actualTypeArgs[0]
.equals(GPSTrackerCollection.class));
} else {
isWritable = false;
}

return isWritable;
}

@Override
public void writeTo(List<GPSTrackerCollection> gpsTrackerCollections,
Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4,
MultivaluedMap<String, Object> arg5, OutputStream os)
throws IOException {

...
}
}

The key thing here is that we check the parameterised type argument for equality against the class of GPSTrackerCollection. My intuition was that I could do an instanceof operation here, but this is not permitted. Check out this FAQ as to why.

Enjoy.