we will build a custom entity provider for handling the request and response message content represented using the application/csv media type. Note that JAX-RS, by default, does not support the CSV media type. The custom entity providers, which we will build in a short while, leverage the runtime extension mechanism offered by JAX-RS via the MessageBodyWriter and MessageBodyReader interfaces.
The JAX-RS framework uses MessageBodyWriter to serialize the Java representation of resources returned by the REST web API into an appropriate format, which is sent back to the client. The JAX-RS runtime natively supports the serialization of the commonly used Java types, such as java.lang.String, java.io.InputStream, and Java custom objects annotated with JAXB binding annotations. You can provide your own implementation of MessageBodyWriter if you find that the default implementation provided by JAX-RS is not meeting your use case requirements.
A javax.ws.rs.ext.MessageBodyWriter<T> provider should implement the following methods:
- isWriteable(): This method checks whether this MessageBodyWriter implementation supports converting the Java type present in the method argument to the designated internet media type. This method is invoked when the framework tries to find a MessageBodyWriter implementation for serializing the Java objects returned by a resource method to the designated internet media type.
- getSize(): JAX-RS 2.0 deprecated this method, and the value returned by the method is ignored by the JAX-RS runtime. You can return -1 from this method.
- writeTo(): This method writes a Java type object to an HTTP message.
Keep a note of the following points when building a MessageBodyWriterimplementation:
- A MessageBodyWriter provider implementation may be annotated with @Produces to restrict the media types for which it will be considered suitable
- A MessageBodyWriter provider implementation must be either programmatically registered in the JAX-RS runtime or must be annotated with @Provider annotation
The following example shows how you can build a custom MessageBodyWriter implementation that converts a list of the Department Java objects into the CSV format:
The following example shows how you can build a custom MessageBodyWriter implementation that converts a list of the Department Java objects into the CSV format:
//Other imports omitted for brevity
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
@Provider
@Produces("application/csv")
public class CSVMessageBodyWriter implements MessageBodyWriter<List<Department>> {
/**
* Ascertain if MessageBodyWriter supports
* a particular type.
*/
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
//Is this MessageBodyWriter implementation capable
//of serializing the object type returned by
//the current REST API call?
return (List.class.isAssignableFrom(type));
}
/**
*Deprecated by JAX-RS 2.0 and ignored by Jersey runtime
*/
@Override
public long getSize(List<Department> t, Class<?> type, Type
genericType, Annotation[] annotations,
MediaType mediaType) {
return 0;
}
/**
* Converts Java to desired media type and Writes it
* to an HTTP response
*/
@Override
public void writeTo(List<Department> dataList, Class<?> type,
Type genericType, Annotation[] annotations, MediaType
mediaType, MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
// This class uses CsvBeanWriter for converting
// Java to CSV. It is an open source framework
// that writes a CSV file by mapping each field
// on the bean to a column in the CSV file
//(using the supplied name mapping).
ICsvBeanWriter writer = new CsvBeanWriter(
new PrintWriter(entityStream),
CsvPreference.STANDARD_PREFERENCE);
//No data then return
if (dataList == null || dataList.size() == 0) {
return;
}
//Columns headers in CSV
String[] nameMapping ={"departmentId","departmentName",
"managerId","locationId"} ;
//CsvBeanWriter writes the header with the property names
writer.writeHeader(nameMapping);
for (Object p : dataList) {
//Write each row
writer.write(p, nameMapping);
}
writer.close();
}
}
The following RESTful web service call illustrates a sample output (in CSV format) generated by the CSVMessageBodyWriter implementation:
GET /api/hr/departments HTTP/1.1
Host: localhost:8080
Accept: application/csv
departmentId,departmentName,locationId,managerId
1001,"Finance",1700,101
1002,"Office Administration",1500,205
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 MessageBodyReaderimplementation 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
Comments
Post a Comment