Terminiamo la serie di articoli su JAX-RS iniziata con i post Primi Passi con JAX-RS e Iniezione dei Parametri in JAX-RS approfondendo alcune caratteristiche avanzate del framework.
Request e Response XML
Tutti i servizi REST di esempio che sono stati presentati negli articoli precedenti accettavano come parametri formali dei tipi primitivi ed analogamente restituivano sempre un tipo primitivo. Vediamo ora come utilizzare il framework per la gestione di oggetti complessi. Innanzitutto deve essere scelto il modo in cui l’oggetto deve essere serializzato al fine di essere trasferito dal client al server e viceversa. Una possibilità è quella di utilizzare l’XML e per farlo avremo bisogno di utilizzare la specifica JAXB (Java Architecture for XML Binding).
Procediamo quindi definendo la classe Message
come segue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Message { private static final AtomicLong counter = new AtomicLong(100); private Long id; private String text; private String author; private Date date; public Message(){} public Message(Date date, String author, String text ) { super(); this.text = text; this.author = author; this.date = date; this.id = counter.getAndIncrement(); } // Getter and Setter } |
MessageService
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Path("messages") public class MessageService { private static final List<Message> messages = new ArrayList<Message>() {{ add( new Message( new Date(), "javaboss", "Message n. one" ) ); add( new Message( new Date(), "javaboss", "Message n. two" ) ); }}; @GET @Produces( MediaType.APPLICATION_XML ) public List<Message> getMessages() { return MessageService.messages; } } |
La prima differenza rispetto agli esempi precedenti è la presenza dell’annotazione @Produces
che è utilizzata per indicare il MIME type del tipo restituito dal servizio. Il valore di default è “text/plain” quindi testo “semplice”; nel nostro caso però deve essere “application/xml“. Jersey fornisce la classe di utilità MediaType
in cui sono codificati tutti i possibili MIME type.
Se proviamo ad avviare il servizio ed accediamo all’url http://localhost:8080/myapp/messages noteremo sul log l’errore: MessageBodyWriter not found for media type=application/xml,
che indica che JAX-RS non trova il convertitore adatto per il tipo MIME specificato. Come anticipato precedentemente abbiamo bisogno di utilizzare JAXB per eseguire la serializzazione, che fortunatamente è già inserito in Jersey. Per attivarlo dobbiamo semplicemente annotare la classe Message
con @XmlRootElement
:
1 2 3 4 |
@XmlRootElement public class Message { ... } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<messages> <message> <author>javaboss</author> <date>2016-09-08T12:18:18.040+02:00</date> <id>100</id> <text>Message n. one</text> </message> <message> <author>javaboss</author> <date>2016-09-08T12:18:18.040+02:00</date> <id>101</id> <text>Message n. two</text> </message> </messages> |
Implementiamo ora il resource method che consente di inserire un nuovo messaggio. In questo caso per coerenza utilizzeremo il metodo HTTP POST e l’annotation @Consumes
per indicare il MIME type di dell’oggetto passato al metodo come parametro formale:
1 2 3 4 5 6 7 8 |
@POST @Produces( MediaType.APPLICATION_XML ) @Consumes( MediaType.APPLICATION_XML ) public Message addMessage( Message message ) { Message msg = new Message( new Date(), message.getAuthor(), message.getText() ); messages.add( msg ); return msg; } |
Se utilizzate ARC di chrome per eseguire il test assicuratevi di specificare “application/xml” come content type ed inserire l’XML del messaggio nel modo mostrato nell’immagine seguente.
Status Code Created
Analizzando meglio il servizio addMessage
ci rendiamo conto che lo status code restituito, in assenza di errori, è il 200. Il protocollo HTTP prevede però uno specifico status code 201 per la creazione di nuove risorse. Inoltre l’URI della risorsa dovrebbe essere inserito nell’header della response alla proprietà Location
. Vediamo come farlo nella seguente versione del metodo:
1 2 3 4 5 6 7 8 9 10 11 |
@POST @Produces( MediaType.APPLICATION_JSON ) @Consumes( MediaType.APPLICATION_JSON ) public Response addMessage( Message message, @Context UriInfo uriInfo ) { Message msg = new Message( new Date(), message.getAuthor(), message.getText() ); messages.add( msg ); URI location = uriInfo.getAbsolutePathBuilder().path( msg.getId().toString() ).build(); return Response.status( Status.CREATED ).header( "Location", location ).entity( msg ).build(); } |
Utilizzando l’oggetto Response
, che implementa il pattern builder, è possibile inserire lo status code Status.CREATED
ovvero 201 e l’header Location
. L’URI corrispondente alla risorsa creata è invece ottenuta a partire dall’oggetto UriInfo,
iniettato nel metodo attraverso l’annotazione @Context
, il quale implementa un UriBuilder
che è utilizzato per apporre al path assoluto della risorsa corrente, l’id del messaggio creato. L’URI così generato sarà quindi sarà del tipo http://localhost:8080/myapp/messages/102.
Si noti che l’oggetto Response
fornisce un metodo di utilità che consente di realizzare quanto detto in modo compatto:
1 2 3 4 |
... // setta lo status code a 201 e Location al valore indicato return Response.created( location ).entity( msg ).build(); ... |
Request e Response JSON
Nello stesso modo è possibile utilizzare JSON come tecnologia di serializzazione degli oggetti trasferiti tra server e client. Per farlo è sufficiente specificare il media type “application/json” e inserire nel pom la dipendenza al modulo Jersey MOXy. Se avete generato il progetto con maven sarà sufficiente decommentarlo:
1 2 3 4 |
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-moxy</artifactId> </dependency> |
/messages
sarà quindi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[ { "author": "javaboss", "date": "2016-09-08T14:14:21.89+02:00", "id": 100, "text": "Message n. one" }, { "author": "javaboss", "date": "2016-09-08T14:14:21.89+02:00", "id": 101, "text": "Message n. two" } ] |
Download di File
Vediamo ora come sia possibile implementare un servizio REST in modo che restituisca al client un file quando invocato. Per farlo è necessario:
- Configurare opportunamente il MIME type attraverso l’annotazione
@Produces
in funzione del tipo di file restituito; - Configurare nell’header della response la proprietà Content-Disposition in modo da indicare il nome del file ed istruire il browser ad aprire la pop up di download.
Nel caso di file di testo il metodo apparirà quindi come segue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Path("files") public class FileService { @GET @Path("/get/text") @Produces("text/plain") public Response getTextFile() { File file = new File( this.getClass().getClassLoader().getResource("text.txt").getFile() ); ResponseBuilder response = Response.ok((Object) file); response.header("Content-Disposition", "attachment; filename=\"text.txt\""); return response.build(); } } |
Per altre tipologie di file i MIME type da utilizzare sono:
Immagini | image/png |
application/pdf | |
Excel | application/vnd.ms-excel |
Upload di File
Per eseguire l’upload di un file verso un servizio REST il MIME type definito nell’annotazione @Consumes del servizio deve essere “multipart/form-data“. Nel progetto va anche inserita la dependency Jersey per la gestione di tali tipologie di formati:
1 2 3 4 |
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-multipart</artifactId> </dependency> |
Il metodo di upload sara quindi così definito:
1 2 3 4 5 6 7 8 9 10 11 12 |
@POST @Path("/upload") @Consumes(MediaType.MULTIPART_FORM_DATA) public Response uploadFile( @FormDataParam("file") InputStream uploadedInputStream, @FormDataParam("file") FormDataContentDisposition fileDetail) { // save it writeToFile(uploadedInputStream, "c://uploaded/" + fileDetail.getFileName()" ); return Response.status( Response.Status.OK ).entity( "File Uploaded!!" ).build(); } |
Download Codice Sorgente
Il progetto completo degli esempi riportati nell’articolo è disponibile qui restful-ws.