In the previous section, we discussed the entity provider that supports the marshaling of the Java object to media types. In this section, we will see the entity provider that does the reverse process, unmarshaling of the input stream to the Java types.
The JAX-RS framework uses MessageBodyReader to deserialize the message body into the Java type. The JAX-RS runtimes natively supports the deserialization of input stream to the commonly used Java types. You can use the custom MessageBodyReader implementation to control the deserialization of the input stream, which is not supported by JAX-RS, by default.
A javax.ws.rs.ext.MessageBodyReader<T> provider should implement the following methods and contracts:
- isReadable(): This method checks whether the MessageBodyReader interface can produce an instance of a particular Java type. This method is invoked when the framework tries to find a matching MessageBodyReader interface for reading the input stream into a Java type parameter present in the resource method.
- readFrom(): This method reads the input stream into the designated Java type.
Keep a note of the following points when building a MessageBodyReader interface:
- A MessageBodyReader implementation may be annotated with @Consumes to restrict the media types for which it will be considered suitable
- A MessageBodyReader provider implementation must be either programmatically registered in the JAX-RS runtime or must be annotated with the @Provider annotation
The following example illustrates a custom MessageBodyReader implementation, which converts the CSV representation of the department items present in the input stream to list the Department Java objects:
//Other imports are omitted for brevity import javax.ws.rs.Consumes; 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.Provider; import org.supercsv.cellprocessor.ift.CellProcessor; import org.supercsv.io.CsvBeanReader; import org.supercsv.io.ICsvBeanReader; import org.supercsv.prefs.CsvPreference; @Provider @Consumes("application/csv") public class CSVMessageBodyReader implements MessageBodyReader<List<Department>> { /** * Ascertain if the MessageBodyReader can produce * an instance of a particular type. */ @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Collection.class.isAssignableFrom(type); } /** * Read a type from InputStream. */ @Override public List readFrom(Class<List<Department>> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { ArrayList list = new ArrayList(); //Following code uses Super CSV lib for reading CSV data //Define the type for each column in CSV final CellProcessor[] processors = new CellProcessor[]{ new NotNull(new ParseShort()), // departmentId new NotNull(), // departmentName new NotNull(new ParseShort()), // locationId new Optional(new ParseInt()) //managerId }; //Reads CSV input stream ICsvBeanReader beanReader = new CsvBeanReader(new InputStreamReader(entityStream), CsvPreference.STANDARD_PREFERENCE); //Start building object from CVS String[] header = beanReader.getHeader(false); Object obj = null; while ((obj = beanReader.read(Department.class, header, processors)) != null) { list.add(obj); logger.log(Level.INFO, obj.toString()); } return list; } }
The following RESTful web service call illustrates how a JAX-RS application can use CSVMessageBodyReader for taking input in CSV format:
POST /api/hr/departments HTTP/1.1 Host: localhost:8080 Content-Type: application/csv departmentId,departmentName,locationId,managerId 1001,"Finance",1700,101 1002,"Office Administration",1500,205