REST en JAVA avec la JSR-311

La JSR 311 JAX-RS [JavaTM API for RESTful Web Services] est le pendant REST de la JSR 224 JAX-WS [JavaTM API for XML-Based Web Service]. Elle marque la volonté de la part de la communauté Java de cadrer, tout comme pour la stack WS-*, le développement des applications JAVA orientées ressources. Bien qu’étant sur le point d’être finalisée (dernier stade : Final Approval Ballot), elle est déjà implémentée par la plupart des frameworks REST du moment (Jersey, RESTeasy, CXF, une extension Restlet…).

La suite de ce billet présente les annotations de la JSR en regard des principes REST qu’elles mettent en œuvre.

Quels sont donc les apports de cette JSR dans le développement d’applications Java orientées ressources ? Afin de respecter le style architectural REST, il est nécessaire de suivre certaines règles. Ces règles vous pouvez les retrouver dans la thèse de Roy Fielding, le livre Restful Web Services ou avec une simple recherche sur le Web. Dans cet article, je me suis librement inspiré d’une présentation de Paul Sandoz (commiteur Jersey / JSR-311) à Java One. Les exemples de code présentés utilisent Jersey [1].

Principe 1 – Give everything an ID : Une ressource / un identifiant

La ressource REST

En REST, une ressource représente une chose suffisamment importante à vos yeux pour être référencée. Ce n’est pas obligatoirement l’exposition d’un tuple de votre base de données. Cela peut tout aussi bien être le résultat d’un algorithme, un document, une liste d’objets sérialisés, … De manière générale, tout ce qui peut être stocké sur un ordinateur ou représenté sous la forme d’un flux de bits peut être considéré comme une ressource.

Les exemples présentés ci-après ont été développés grâce à Jersey, la RI (Reference Implementation) de Sun, qui semble à l’heure actuelle, être le framework REST plus mature. La version 1.0 devrait paraître à la fin du mois de Septembre. Il est dors et déjà utilisable en version 0.9 avec de multiples fonctionnalités (intégration avec Spring, client de test, …). Le but de cet article n’étant pas d’exposer les fonctionnalités de Jersey, nous resterons concentrés sur les spécificités de la JSR-311. Un article dédié à l’utilisation de Jersey paraîtra prochainement sur ce même blog.

On référence une ressource en lui associant un identifiant unique : on s’appuie sur la notion d’URI du protocole HTTP. C’est le moyen univoque d’accéder à une ressource.

Quelques exemples d’URI

  • http://www.monserveur.com/customers : Pointe sur l’ensemble des clients

  • http://www.monserveur.com/customers/12 : Pointe sur le client dont l’identifiant technique est 12

  • http://www.monserveur.com/orders/XXXX/customers : Pointe sur les clients dont l’ordre à pour identifiant XXXX

Annotation @Path

La JSR-311 définit l’annotation @Path : elle permet de lier une ressource à une URI.

1
2
3
4
5
@Path(/customers”)
public class CustomersResource {
@GET
public Customer getCustomer() {}
}
On accède à la racine de la ressource Customers par le chemin http://monServeur/monContexte/customers

Annotations @Path, @PathParam, @QueryParam

L’annotation @Path en combinaison avec les @PathParam / @QueryParam permet de gérer les URLs paramètrables.

Vous souhaitez accéder à une ressource Customer, dont l’identifiant unique est son adresse mail. On souhaite donc effectuer une requête du type : GET http://monServeur/monContexte/customers/toto@toto.com

1
2
3
4
5
6
7
@Path(/customers”)
public class CustomersResource {
@GET
@Path(/{email})
public Customer getCustomer(@PathParam(“email”) String emailAddress)
{}
}
@Path gère donc les URLs paramétrables de type : http://monServeur/monContexte/customers/{email}. @PathParam(« email ») récupère la valeur {email} contenue dans URL paramètrable de ce type.

De même, @QueryParam(« maVar ») retourne la valeur du paramètre de l’url http://monServeur/monContexte/customers/{email}?maVar=12.

Principe 2 – Use standard methods : utilisation des méthodes HTTP

Les architectures REST doivent respecter le standard HTTP, tant dans les codes retours que dans l’utilisation même des verbes HTTP (définie par la RFC 2616 ).

Méthodes
But
GET

Accéder à une ressource, potentiellement en cache.

POST

Créer une ressource (identifiant inconnu)

PUT

Créer / Mettre à jour une ressource (identifiant connu)

DELETE
Supprimer une ressource
HEAD

Identique à GET mais aucun contenu dans le corps la réponse. Utile notamment pour vérifier la validité d’une ressource, son accessabilité, …

Ces méthodes permettent de manipuler des ressources désignées par leur URI .

  • GET sur http://www.monserveur.com/customers : Totalité des clients
  • POST sur http://www.monserveur.com/customers : Création d’un client

@GET, @POST, @DELETE, @PUT, @HEAD

1
2
3
@GET
@Path(/{email})
public Customer getCustomer(@PathParam(“email”) String emailAddress){}

Dans l’exemple ci-dessus, l’annotation @GET, associée au @Path, permet de router toutes les requêtes de type GET /customers/{email} sur la méthode getCustomer(String email). @GET, @POST, @DELETE, @PUT, @HEAD agissent simplement en combinaison de @PATH comme un dispatcher.

Principe 3 – Link things together : Lier les ressources entre elles.

Afin de permettre la navigabilité entre les ressources exposées, il est nécessaire de les lier entres elles.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Context
private UriInfo uriInfo; // Injecté directement dans la ressource// Récupere le path courant
UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
 
// Crée l’uri pour accéder à la nouvelle ressource
URI uri = uriBuilder.path(customer.getId()).build();
 
// Retourne la réponse avec la nouvelle ressource
return Response.created(uri).entity(customer).build();
 
}

Dans cet exemple, on utilise les champs location et entity définit dans une réponse HTTP :

  • location : l’uri de nouvelle ressource Customer

  • entity : représentation de la ressource Customer créé.
Principe 4 – Multiple representations : Multiplicité des représentations

Pour une ressource, identifiée par une URI, on peut avoir de multiples représentations.

Les plus courantes étant :

  • XML

  • JSON

  • (X)HTML

  • ATOM

Ce principe repose sur la négociation de contenu possible grâce à HTTP. A l’envoi d’une requête HTTP, le champ Accept défini dans le header de la requête http, peut prendre plusieurs valeurs (classés par ordre de préférences) et permet de choisir le format de représentation mise à disposition pour la ressource.

Exemple d’un GET sur maps.google.fr avec Firefox


GET Host maps.google.fr
User-Agent Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
Connection keep-alive
Referer http://news.google.fr/nwshp?hl=fr&tab=wn

@Produces, @Consumes

On accède à partir d’une unique URL à une multiplicité de représentations d’une même ressource. On utilisera les annotations @Produces et @Consumes pour gérer en entrée / sortie les multiples représentations d’une ressource.

  • @Produces : désigne le format de représentation de la ressource.

  • @Consumes : désigne le format de consommation de la méthode Java.

1
2
3
4
5
6
7
8
9
10
11
@GET
 
@Consumes({”application/xml”})
 
@Produces({”application/xml”,”application/json”})
 
Order getOrder(@PathParam(”order_id”) String id) {
 
…
 
}

La méthode getOrder consomme du XML et peut produire en fonction du client, un représentation XML ou JSON.

@Provider

La notion de Provider peut intervenir lorsque l’on souhaite gérer finement la représentation de ses ressources. En effet, la majorité des frameworks REST offre de base la capacité de sérialiser/désérialiser des beans JAXB (soit en utilisant des annotations, soit générés à partir d’un XSD). Le Provider autorise un traitement spécifique en entrée et / ou en sortie sur un objet Java.

1
2
3
4
5
6
7
8
9
@Produces(”text/plain”)
 
Content getContent(){
 
…
 
return content;
 
}

Dans cet exemple, une méthode retourne une représentation text/plain d’une ressource Content. Pour maitriser le formatage en sortie de cet objet, nous pouvons définir notre propre Provider de Content.

1
2
3
4
5
6
7
8
9
10
@Produces(”text/plain”)
@Provider
public class ContentProvider implements MessageBodyWriter {
 
public void writeTo(Content p,Class type, Type genericType, Annotation annotations[],MediaType mediaType, MultivaluedMapOutputStream out) throws IOException {
 
out.write([Content] :+ content.toString());
 
}}

Il nous est possible d’utiliser le même méchanisme pour créer une représentation HTML si notre client est un browser, du JSON si c’est un client AJAX, du XML si c’est une application, etc.

@Provider / ExceptionMapper

Un Provider peut vous être utile pour traiter les cas d’erreur. En effet, il est possible de mapper une exception vers une réponse HTTP spécifique. Vous n’avez surement pas envie de polluer votre code d’exposition avec de multiples try / catch pour renvoyer :

  • si c’est une IllegalArgumentException : un BAD_REQUEST

  • si c’est un CustomerNotFound : un NOT_FOUND

Pour se faire, le composant ExceptionMapper est un Provider qui permet d’attraper toutes les exceptions d’un certain type et de renvoyer une réponse selon vos désidératas :

Par exemple, vous souhaitez renvoyer BAD_REQUEST dès qu’est levée une exception de type IllegalArgumentException.

1
2
3
4
5
6
7
8
9
10
11
@Provider
public class RuntimeMapper implements ExceptionMapper {
 
private Logger logger = Logger.getLogger(getClass());
 
public Response toResponse(IllegalArgumentException e) {
 
return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
}
}
}

Conclusion

Comme évoqué précédemment la JSR-311 encadre le développement des applications REST. Les annotations permettent de mettre en oeuvre certains principes REST de manière concise, le code d’exposition s’en trouvant par la même occasion allégé. En lisant le code JAVA d’une ressource, il est très simple de voir à quelle URI elle est attachée, le format de consommation / exposition sans être gêné par la génération des codes d’erreurs ou l’étape de marshalling / unmarshalling des ressources par exemple. Cependant, ce n’est qu’un framework technique à la disposition du développeur. Le design de ressources reste une étape primordiale dans la mise en oeuvre d’une architecture orientée ressources. La JSR ne générera pas pour vous une architecture RESTful mais vous permettra de les exposer au plus vite.

[1]
Les exemples présentés ont été développés grâce à Jersey , la RI (Reference Implementation) de Sun, qui semble à l’heure actuelle, être le framework REST plus mature. La version 1.0 devrait paraître à la fin du mois de Septembre. Il est dors et déjà utilisable en version 0.9 avec de multiples fonctionnalités (intégration avec Spring, client de test, …). Le but de cet article n’étant pas d’exposer les fonctionnalités de Jersey, nous sommes restés concentrés sur les spécificités de la JSR-311. Un article dédié à l’utilisation de Jersey paraîtra prochainement sur ce même blog.