rest - upjv
TRANSCRIPT
Architecture des SI - partie 2 - année 2014
Didier FERMENT - Université de Picardie
REST
"Representational State Transfer" est une architecture orienté ressource suggéré par Roy T. Fielding (co-auteur d'Apache)
• donc basé sur HTTP, les URIs, les liens ....
• c'est un mécanisme analogue à RPC (Remote Procedure Calls) et les Web Services (SOAP, WSDL, …) mais beaucoup plus simple.
• Ce n'est pas un "standard W3C".
• Roy T. Fielding a définit des règles à respecter pour une architecture RESTful
• Règle : les ressources sont identifiées par URI Exemple :
• http://nirvana.com/prophetes identifie la liste des prophètes
• http://nirvana.com/prophetes/13 identifie le 13-ème prophète
• http://nirvana.com/prophetes/brian identifie le prophète brian
• http://nirvana.com/prophetes/13/propheties identifie la liste des prophéties du13-ème prophète
• Règle : les ressources sont manipulées au travers des représentations
• une ressource peut avoir des représentations dans des formats divers : XML,CSV, JSON, HTML, CSV, ....
• Le client peut définir le(les) format de réponse souhaitée via l’entête Accept.
• Exemple :
GET /prophetes/13 HTML/1.1 Host: nirvana.com Accept: application/xml
• Règle : Utiliser les verbes HTTP comme identifiant des opérations
• HTTP propose les verbes correspondant aux 4 opérations CRUD :
• Créer (create) => POST
• Afficher (read) => GET
• Mettre à jour (update) => PUT
• Supprimer (delete) => DELETE
• Exemple :
• GET http://nirvana.com/prophetes lit la liste des prophètes
• DELETE http://nirvana.com/prophetes/13 supprime le 13-ème prophète
• POST http://nirvana.com/prophetes + body des données pour créer un prophète
• PUT http://nirvana.com/prophetes/13 + body des données pour modifier le 13-ème prophète
• Règle : Stateless : Pas de gestion d'état
• Le serveur ne stocke jamais l’état des applications donc des requêtes.
• Simplifie le service mais augmente le volume de communication !
• L'alternative est fournie par la règle suivante : HATEOAS
• Règle : Hypermedia as the Engine of Application State (HATEOAS)
• Les liens hypermédia doivent permettre l’enchaînement des actions donc le changement d'état
• exemple : l'URI de suppression d'un prophète propose, en retour, un lien d'URI pour en faire un saint.
DELETE /prophetes/13 HTML/1.1
Host: nirvana.com Accept : application/xml HTTP/1.1 200 OK ContentType :application/xml <status>stopped as martyr</status> <link rel="beatification" method="post" href="prophetes/13/beatifier" />
web-ographie :
• http://opikanoba.org/tr/fielding/rest/chapitre 5 de la thèse de Roy T. Fielding sur REST
• http://rest.elkstein.org/2008/02/what-is-rest.html
présentation simple de REST
• http://mbaron.developpez.com/soa/jaxrs/
Développer des Services Web REST avec Java : JAX-RS de Mickael BARON
• http://docs.oracle.com/javaee/6/tutorial/doc/giepu.html
The Java EE 6 Tutorial : Chapter 20 - Building RESTful Web Services with JAX-RS
• https://jersey.java.net/documentation/latest/index.htmldocumentation de l'Api Jersey implémentation de JAX-RS
Api Jersey Api Jersey pour service Restful
• JAX-RS = spécification par le Java Community Process pour les services Restful en Java
• décrit uniquement le coté serveur• javax.ws.rs.* = package de JAX-RS• implémentations : XCF d'Apache, RestEasy de Jboss, ...
• Jersey est l'implémentation d'Oracle pour cette spécification• fonctionne sur Tomcat, GlassFish, JBoss, ….• construit avec Apache Maven : ce qui simplifie la dépendance entre APIs• fournit une Api coté client.
Installation
• requis : Java 7• Tomcat 7 : http://tomcat.apache.org/
téléchargez puis décompressez• Eclipse Standard/SDK Version: 4 .3.2 Kepler
http://www.eclipse.org/• téléchargez puis décompressez puis démarrez• Installez Web, XML, JavaEE and OSGi Entreprise Development :
Help → install new software → work with → Kepler - http://download.eclipse.org/releases/keplerCochez -> Web, XML, JavaEE and OSGi Entreprise Development → … finissez
• Installez Maven for Eclipse :Help → install new software → Add repository → name : M2Eclipse → location : http://download.eclipse.org/technology/m2e/releasesCochez → Maven for Eclipse→ … finissez
• Maven est un successeur de make d'Unix et d'Ant d'Apache pour gérer le cycle de vie d'un projet logiciel
• Le fichier de description du projet est pom.xml :project object model. Il contient des dépendances avec des ressources internes ou externes et les séquences de compilation, test, installation, …
• Maven est capable de récupérer des ressources via le réseau dans des dépôts, notamment suite à des dépendances transitives. C'est ce point fort que nous allons exploiter
• Un projet Maven a sa propre structure arborescente évidemment différente d'un projet Eclipse. Le plugin assure une assez bonne cohérence des 2.
Premier Service : le classique hello
• View → Open Perspective → Java EE• File -> New → Maven Project
→ maven-archtype : "Internal Catalog" maven-archtype-webapp → groupId : testJersey → artifactId : hello
• Project Explorer → hello → src -> add folder --> src/main/java → Java ressources -> src/main/java -> New --> Package df.jersey.helloserver
• → pom.xml : ajoutez les dépendances Jersey nécessaires au projet <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jerseycontainerservlet</artifactId> <version>2.8</version> </dependency>
• → Java ressources -> src/main/java -> df.jersey.helloserver → new class Hello• rmq : les sources se trouvent dans src/main/java dans un projet Maven
package df.jersey.helloserver;
import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType;
@Path("/bonjour") public class Hello {
@GET @Produces(MediaType.TEXT_PLAIN) public String sayPlainTextHello() { return "bonjour"; } @GET @Produces(MediaType.TEXT_XML) public String sayXMLHello() { return "<?xml version=\"1.0\"?>" + "<hello> bonjour" + "</hello>"; }
@GET @Produces(MediaType.TEXT_HTML) public String sayHtmlHello() {
return "<html> " + "<title>" + "bonjour" + "</title>" + "<body><h1>" + "bonjour" + "</h1></body>" + "</html> "; }
}
• → src/main/webapp/WEB-INF → editer web.xml : • fichier descripteur de déploiement de l'application web : il contient les
caractéristiques de l'application, les servlets utilisées, les paramètres d'initialisation ….
<!DOCTYPE webapp PUBLIC "//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/webapp_2_3.dtd" >
<webapp> <displayname>Hello</displayname> <servlet> <servletname>Hello</servletname> <servletclass>org.glassfish.jersey.servlet.ServletContainer</servletclass> <initparam> <paramname>jersey.config.server.provider.packages</paramname> <paramvalue>df.jersey.helloserver</paramvalue> </initparam> <loadonstartup>1</loadonstartup> </servlet> <servletmapping> <servletname>Hello</servletname> <urlpattern>/rest/*</urlpattern> </servletmapping> </webapp>
• → Run on server → manually define ... → Apache → tomcat7 → next → Tomcat installation directory →chemin/absolu/jusqua/apache-tomcat-7.0.54
• Désormais, vous pourrez ré-utiliser le serveur défini.• Dans un Navigateur :
http://localhost:8080/hello/rest/bonjourbonjour
• Voici la capture des échanges HTTP par wireshark :GET /hello/rest/bonjour HTTP/1.1 Host: localhost:8080 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ...Accept-Encoding: gzip,deflate,sdch Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html Content-Length: 66 Date: Sat, 14 Jun 2014 07:45:22 GMT
<html> <title>bonjour</title><body><h1>bonjour</h1></body></html>
• Les éléments du service REST hello :• Le projet se nomme hello
• donc par défaut c'est la base du chemin de la ressource REST• La classe Hello du package df.jersey.helloserver est un POJO :
• Plain Old Java Object, sans les lourdeurs d'écriture d'un bean
package df.jersey.helloserver;
@Path("/bonjour") public class Hello {
@GET @Produces(MediaType.TEXT_HTML) public String sayHtmlHello() { return "<html> " + "<title>" + "bonjour" + "</title>" + "<body><h1>" + "bonjour" + "</h1></body>" + "</html> "; } ...
• Les annotations JAX-RS passurent la jonction entre la classe POJO et la requête ou réponse HTTP :
• @Path spécifie de répondre aux requêtes d'URI finissant par bonjour• le nom de la méthode sayPlainTextHello ne sert à rien !
• @GET spécifie que la méthode répond à une requête GET• @Produces spécifie le type de la réponse• return fournit la réponse
• le fichier web.xml décrit la servlet :... <servlet> <servletname>Hello</servletname> <servletclass>org.glassfish.jersey.servlet.ServletContainer</servletclass> <initparam> <paramname>jersey.config.server.provider.packages</paramname> <paramvalue>df.jersey.helloserver</paramvalue> </initparam>
<loadonstartup>1</loadonstartup> </servlet> <servletmapping> <servletname>Hello</servletname> <urlpattern>/rest/*</urlpattern> </servletmapping> </webapp>
• La servlet-class est celui de la servlet container Jersey• le param-value spécifie le package des classes POJO du service :
df.jersey.helloserver• l'url-pattern spécifie les patterns d'uri qui peuvent être "servi"• le servlet-name doit être identique dans les 2 sections : servlet et servlet-
mapping
• donc l'appel get http://localhost:8080/hello/rest/bonjour• comportant un Accept: text/html, ….
• déclenche la servlet hello• qui utilise la servlet container Jersey
• si l'uri est rest/*• qui recherche dans le package df.jersey.helloserver
• une classe et méthode pour un GET avec, dans l'uri, bonjour
• comme il y a plusieurs GET possible, celui qui produit du text Html est choisie :
• la réponse est :<html> <title>bonjour</title><body><h1>bonjour</h1></body></html>
Client Jersey pour le service hello
• File -> New → Maven Project → maven-archtype : "Internal Catalog" maven-archtype-quickstart → groupId : testJersey → artifactId : client-hello
• → pom.xml : <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rsapi</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jerseyclient</artifactId> <version>2.9</version> </dependency>
• Project Explorer → client_hello → src/main/java → df.jersey.helloserver →App.java : éditez
package testJersey.client_hello;
import javax.ws.rs.client.Client;import javax.ws.rs.client.ClientBuilder;import javax.ws.rs.client.Invocation;import javax.ws.rs.client.WebTarget;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Response;
public class App { public static void main(String[] args) { Client client = ClientBuilder.newClient(); WebTarget webTarget = client.target("http://localhost:8080/hello"); WebTarget restTarget = webTarget.path("rest"); WebTarget helloTarget = restTarget.path("bonjour"); Invocation.Builder invocationBuilder = helloTarget.request(MediaType.TEXT_PLAIN_TYPE); Response response = invocationBuilder.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
Invocation.Builder invocationBuilder2 = helloTarget.request(MediaType.TEXT_XML_TYPE); response = invocationBuilder2.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
Invocation.Builder invocationBuilder3 = helloTarget.request(MediaType.TEXT_HTML_TYPE); response = invocationBuilder3.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println(); }}
• → Run → Java Application :200 bonjour
200 <?xml version="1.0"?><hello> bonjour</hello>
200 <html> <title>bonjour</title><body><h1>bonjour</h1></body></html>
Passage de paramètre dans la requête GET
• Coté serveur :• ajoutons dans la classe Hello :
@Path("/untel/{nom}") @GET @Produces(MediaType.TEXT_PLAIN) public String sayPlainTextHello2(@PathParam("nom") String nom) {
return "bonjour " + nom; }
• Coté client :• ajoutons à la fin de la classe App :
WebTarget bonjourUntelTarget = webTarget.path("rest").path("bonjour") .path("untel").path("bilou"); Invocation.Builder invocationBuilder3 = helloTarget.request(MediaType.TEXT_PLAIN_TYPE); Response response = invocationBuilder3.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
• l’exécution donne
200 bonjour bilou
Passage de paramètre dans la partie ?param=valeur
• Coté serveur :• ajoutons dans la classe Hello :
@Path("/telautre") @GET @Produces(MediaType.TEXT_PLAIN) public String sayPlainTextHello3(@DefaultValue("inconnu")
@QueryParam("prenom") String prenom) { return "bonjour " + prenom;
}
• Coté client :• ajoutons à la fin de la classe App :
WebTarget helloTarget = webTarget.path("rest").path("bonjour") .path("telautre"); WebTarget helloWithparamTarget = helloTarget.queryParam("prenom", "bibi"); Invocation.Builder invocationBuilder4 = helloWithparamTarget.request(MediaType.TEXT_PLAIN_TYPE); Response response = invocationBuilder4.get(); ... Invocation.Builder invocationBuilder5 = helloTarget.request(MediaType.TEXT_PLAIN_TYPE); Response response = invocationBuilder5.get(); ...
• l’exécution donne
200 bonjour bibi
200 bonjour inconnu
• l’appel dans un Navigateur :http://localhost:8080/hello/rest/bonjour/telautre?prenom=bibi
bonjour bibi
Les annotations JAX-RS
• @Path :@Path("/bonjour") public class Hello {
@Path("/telautre") @GET public String sayPlainTextHello3( … …
@Path("/untel/{nom}") @GET public String sayPlainTextHello2 ...
• sur la classe, définit une ressource racine :http:hote:port/servlet/chemin/bonjour
• optionnelle sur une méthode, elle s'ajoute à l'URI :http:hote:port/servlet/chemin/bonjour/telautre
• optionnellement, le path peut comporter un template parameter comme { nom } :ainsi la requête d'URI http:hote:port/servlet/chemin/bonjour/bilou fournira la valeurbilou au paramètre nom de la méthode exécutée.
• @PathParam : @Path("/untel/{nom}") @GET public String sayPlainTextHello2(@PathParam("nom") String nom) { ...
• indique que la valeur récupérée dans l'URI est passée à ce paramètre (qui peut n'a pas forcément le même identificateur) :ainsi la requête d'URI http:hote:port/servlet/chemin/bonjour/bilou fournira la valeurbilou au paramètre nom de la méthode exécutée.
• Les types JAVA qui peuvent être passés :1. Type primitif2. String3. Type/Classe ayant un constructeur qui a un seul argument String4. Type/Classe ayant une méthode statique valueOf(String) 5. ...6. ou de classe List<T>, Set<T> ou SortedSet<T>, où T satisfait la condition 2
ou 3 ou 4.
• @QueryParam @DefaultValue : @Path("/telautre") @GET public String sayPlainTextHello3(@DefaultValue("inconnu") @QueryParam("prenom") String prenom) { ...
• permet de récupérer un paramètre dans la partie ? de l'URI :
http://localhost:8080/hello/rest/bonjour/telautre?prenom=bibiet d'affecter une valeur par défaut si elle est omise.
• @Produces @Consumes: @GET @Produces(MediaType.TEXT_PLAIN) public String sayPlainTextHello() { ...
• spécifie le type MIME que la méthode peut produire, respectivement accepter• plusieurs types peuvent figurer
• @GET @POST @PUT @DELETE : @GET public String sayPlainTextHello() { ...
• spécifie le type de requête HTTP que la méthode peut servir• le nom de la méthode ne sert à rien.
• D'autres annotations existent :• @HEAD • @OPTION• @FormParam• @HeaderParam• @CookieParam• @Context
Passer des objets
• JAX-RS permet de passer des objets de type autre que ceux mentionnés plus haut,• en s'appuyant sur la marshall-isation/unmarshall-isation fourni par JAXB (Java
Architecture for XML Binding) : sérialisation d'un objet JAVA en document XML et réciproquement
• JAX-RS est capable aussi de sérialiser/dé-sérialiser en JSON.
• Créez un nouveau projet Maven :• maven-archtype-webapp
ArtifactId : delire0• créez un package df.prophete.delire0.model• créez une classe style "POJO" Prophete :
package df.prophete.delire0.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement public class Prophete {
public Prophete() { super(); this.nom = null; this.id = null;
} public Prophete(String id, String nom) {
super(); this.nom = nom; this.id = id;
} private String nom; private String id; public String getNom() {
return nom; } public void setNom(String nom) {
this.nom = nom; } public String getId() {
return id; } public void setId(String id) {
this.id = id; }
}
• JAXB fournit des annotations qui, appliquées à un POJO, simplifient la transformation.• @XmlRootElement définit la racine du document XML généré à partir de cette
classe.
• Créez un package df.prophete.delire0• Créez une "root resource classe" PropheteRessource :
package df.prophete.delire0;
import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType;
import df.prophete.delire0.model.Prophete;
@Path("/prophete") public class PropheteRessource {
@GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Prophete getProphete() {
Prophete prophete = new Prophete("0", "Zarathoustra"); return prophete;
} }
• La ressource peut fournir une réponse en XML et en JSON• La méthode retourne un objet qui sera automatiquement transformé en
XML ou en JSON pour constituer la réponse HTTP.
• Le fichier pom.xml recoit une nouvelle dépendance MOXy pour la transformation en JSON :
<dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jerseyclient</artifactId> <version>2.8</version> </dependency> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jerseycontainerservlet</artifactId> <version>2.8</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jerseymediamoxy</artifactId> <version>2.8</version> </dependency>
• Coté client :
• créez un projet Maven • avec un package df.prophete.delire0.model comportant la classe Prophete.• Voici la classe App :
package testJersey.clientprophete_delire0;
import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response;
import df.prophete.delire0.model.Prophete;
public class App { public static void main(String[] args) { Client client = ClientBuilder.newClient(); WebTarget delireRestTarget = client.target("http://localhost:8080/delire0"); WebTarget propheteTarget = delireRestTarget.path("rest").path("prophete"); Invocation.Builder invocationBuilder = propheteTarget.request(MediaType.APPLICATION_XML); Response response = invocationBuilder.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
Prophete prophete = invocationBuilder.get(Prophete.class); System.out.println("recuperation objet"); System.out.println("Object Prophete : "+prophete.getId()+","+prophete.getNom()); System.out.println();
Invocation.Builder invocationBuilder2 = propheteTarget.request(MediaType.APPLICATION_JSON); response = invocationBuilder2.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println(); } }
• Exécutez le client après avoir lancé le serveur :200 <?xml version="1.0" encoding="UTF-8" standalone="yes"?><prophete><id>0</id><nom>Zarathoustra</nom></prophete>
recuperation objet Object Prohete : 0, Zarathoustra
200 {"id":"0","nom":"Zarathoustra"}
WADL
• Web Application Description Language• Format de fichier XML pour décrire des applications REST. • Analogue de WSDL en SOAP.
• Jersey génère automatiquement le wadl :• http://localhost:8080/delire-0/rest/application.wadl
<?xml version="1.0" encoding="UTF8" standalone="yes"?> <application xmlns="http://wadl.dev.java.net/2009/02">
<doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.8 20140429 01:25:26"/> <doc xmlns:jersey="http://jersey.java.net/" jersey:hint="This is simplified WADL with user and core resources only. To get full WADL with extended resources use the query parameter detail. Link: http://localhost:8080/delire0/rest/application.wadl?detail=true"/> <grammars> <include href="application.wadl/xsd0.xsd"> <doc title="Generated" xml:lang="en"/> </include> </grammars> <resources base="http://localhost:8080/delire0/rest/"> <resource path="/prophete"> <method id="getProphete" name="GET"> <response> <ns2:representation xmlns:ns2="http://wadl.dev.java.net/2009/02" element="prophete" mediaType="application/xml"/> <ns2:representation xmlns:ns2="http://wadl.dev.java.net/2009/02" element="prophete" mediaType="application/json"/> </response> </method> </resource> </resources> </application>
wadl2java Tool
• Projet GlassFish permettant de générer un squelette de client• https://wadl.java.net/wadl2java.html
• générons un squelette de client : JAVA_HOME=/usr/lib/jvm/java7openjdkamd64 ; export JAVA_HOME ./wadldist1.1.6/bin/wadl2java o . p df.delire0.wadl.client http://localhost:8080/delire0/rest/application.wadl
• Créez un maven project --> maven-archetype-quickstart → ArtifactId : clientwadl_delire0• Importez dans src/main/java le package df.delire0.wadl.client
• Renommez le package car le prefixe "df" est perdu ! • Éditez le pom.xml :
<dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rsapi</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jerseyclient</artifactId> <version>2.9</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jerseyclient</artifactId> <version>1.18.1</version> </dependency>
• Éditez la classe App : package testJersey.clientwadl_delire0;
import df.delire0.wadl.client.Localhost_Delire0Rest; import df.delire0.wadl.client.Prophete; public class App { public static void main(String [] args) {
Localhost_Delire0Rest.Prophete serviceRest = Localhost_Delire0Rest.prophete();
Prophete prophete = serviceRest.getAsPropheteXml(); System.out.println("prophete id = "+prophete.getId()
+" nom = "+prophete.getNom()); } }
• Exécutez :prophete id = 0 nom = Zarathoustra
Un REST-service CRUD
• Gestion de prophètes :chacun aura un nom et un Id.
• GET prophetes donnera la liste des prophètes
• GET prophetes/13 donnera le 13-ème prophète
• DELETE prophetes/13 supprimera le 13-ème prophète
• POST prophetes + body comportant un id et un nom créera un prophète
• PUT prophetes/13 + body comportant un élément XML correspondant à un prophète mettra à jour le 13-éme avec cet item.
• Créez un nouveau projet Maven :• maven-archtype-webapp
ArtifactId : delire1• créez un package df.prophete.delire1.model• créez une classe Prophete comme précédemment.• créez une classe DaoOlympe :
package df.prophete.delire1.model;
import java.util.HashMap;import java.util.Map;
public enum DaoOlympe { instance; private Map<String, Prophete> contentProvider = new HashMap<String, Prophete>(); private DaoOlympe() { Prophete prophete = new Prophete("1", "Zarazoustra"); contentProvider.put(prophete.getId(),prophete); prophete = new Prophete("2", "Brian"); contentProvider.put(prophete.getId(),prophete); prophete = new Prophete("3", "Jesus"); contentProvider.put(prophete.getId(),prophete); } public Map<String, Prophete> getProphetes(){ return contentProvider; }}
• la classe est écrite selon les patterns Data Access Object et donc Singleton
• Editez le POM pour les dépendances Jersey et MOXy• Editez le web.xml
• créez un package df.prophete.delire1• créez une classe PropheteResource
package df.prophete.delire1;
import javax.ws.rs.Consumes;import javax.ws.rs.DELETE;import javax.ws.rs.GET;import javax.ws.rs.PUT;import javax.ws.rs.Produces;import javax.ws.rs.core.Context;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Request;import javax.ws.rs.core.Response;import javax.ws.rs.core.UriInfo;import javax.xml.bind.JAXBElement;
import df.prophete.delire1.model.DaoOlympe;import df.prophete.delire1.model.Prophete;
public class PropheteRessource {
@Context UriInfo uriInfo; @Context Request request; String id; public PropheteRessource(UriInfo uriInfo, Request request, String id) { super(); this.uriInfo = uriInfo; this.request = request; this.id = id; }
@GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Prophete getProphete() { Prophete prophete = DaoOlympe.instance.getProphetes().get(id); if (prophete == null) throw new RuntimeException("Get: Prophete id " + id + " inexistant"); return prophete; }
@GET @Produces({MediaType.TEXT_XML}) public Prophete getPropheteBrowser() { Prophete prophete = DaoOlympe.instance.getProphetes().get(id); if (prophete == null) throw new RuntimeException("Get: Prophete id " + id + " inexistant"); return prophete; }
@PUT @Consumes(MediaType.APPLICATION_XML) public Response putProphete(JAXBElement<Prophete> propheteParam) { Prophete prophete = propheteParam.getValue(); Response res; if(DaoOlympe.instance.getProphetes().containsKey(prophete.getId())) { res = Response.noContent().build(); } else { res = Response.created(uriInfo.getAbsolutePath()).build(); } DaoOlympe.instance.getProphetes().put(prophete.getId(), prophete); return res; } @DELETE public void deleteProphete() { Prophete prophete = DaoOlympe.instance.getProphetes().remove(id); if (prophete == null) throw new RuntimeException("Delete: Prophete id " + id + " inexistant"); }}
• L'annotation @Context injecte une information de la requête HTTP dans la variable.
• L'annotation @Consumes spécifie le type de donnée transmise• La méthode noContent( ) crée une réponse vide• La méthode created( uri ) crée une réponse avec dans le header l'uri passée
• créez une classe ProphetesResourcepackage df.prophete.delire1;
import java.io.IOException;import java.util.ArrayList;import java.util.List;
import javax.ws.rs.Consumes;import javax.ws.rs.FormParam;import javax.ws.rs.GET;import javax.ws.rs.POST;import javax.ws.rs.Path;import javax.ws.rs.PathParam;import javax.ws.rs.Produces;import javax.ws.rs.core.Context;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Request;import javax.ws.rs.core.Response;import javax.ws.rs.core.UriInfo;
import df.prophete.delire1.model.DaoOlympe;import df.prophete.delire1.model.Prophete;
@Path("/prophetes")public class ProphetesRessource { @Context UriInfo uriInfo; @Context Request request;
@GET @Produces(MediaType.TEXT_XML) public List<Prophete> getProphetesListe() { List<Prophete> tousProphetes = new ArrayList<Prophete>(); tousProphetes.addAll(DaoOlympe.instance.getProphetes().values()); return tousProphetes; }
@GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public List<Prophete> getProphetes() { List<Prophete> tousProphetes = new ArrayList<Prophete>(); tousProphetes.addAll(DaoOlympe.instance.getProphetes().values()); return tousProphetes; }
@Path("{id}") public PropheteRessource getProphete(@PathParam("id") String id) { return new PropheteRessource(uriInfo, request, id); }
@POST @Produces(MediaType.TEXT_XML) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public String newProphete(@FormParam("id") String id, @FormParam("nom") String nom) throws IOException { Prophete prophete = new Prophete(id, nom); DaoOlympe.instance.getProphetes().put(prophete.getId(), prophete); return "<?xml version=\"1.0\"?>" + "<links> <href>" + uriInfo.getAbsolutePath() + "/"+ id + " </href> </links>"; }
}
• L'annotation @POST répond à ce type de requete• L'annotation @FormParam permet de récupérer dans le body de la requête
la valeur associée au paramètre selon le format "urlencoded" : paramétre=valeur
• la méthode getProphete( ) est une "sub-resource locator" qui retourne une nouvelle classe ressource : Prophete
• une "sub-resource locator" est annotée par @Path mais pas par une annotation de méthode (@GET, @POST, ...)
• Editez un formulaire de création de prophète : • creerProphete.html dans webapp
<!DOCTYPE html> <html> <head> <meta charset="UTF8"> <title>Insert title here</title> </head> <body> <form action="rest/prophetes" method="POST"> <label for="id">ID</label> <input name="id" /> <br/> <label for="nom">Nom</label> <input name="nom" /> <br/> <input type="submit" value="Submit" /> </form> </body> </html>
• l’appel dans un Navigateur :http://localhost:8080/delire1/rest/prophetes
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><prophetes> <prophete><id>3</id><nom>Jesus</nom></prophete> <prophete><id>2</id><nom>Brian</nom></prophete> <prophete><id>1</id><nom>Zarazoustra</nom></prophete></prophetes>
• l’appel dans un Navigateur :http://localhost:8080/delire1/rest/prophetes/2
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><prophete> <id>2</id> <nom>Brian</nom></prophete>
• l’appel dans un Navigateur :http://localhost:8080/delire1/creerProphete.html
avec les données ci-contreproduit le résultat ci-dessous :
<links><href>http://localhost:8080/delire1/rest/prophetes/4</href>
</links>
• Le Client :• Créez un maven project --> maven-archetype-quickstart
→ ArtifactId : client-delire1• Éditez le POM• créez un package df.prophete.delire1.model
• créez-y une classe Prophete comme précédemment • ou faites un copier-coller du package ...
• Éditez la classe App :package testJersey.client_delire1;
import javax.ws.rs.client.Client;import javax.ws.rs.client.ClientBuilder;import javax.ws.rs.client.Entity;import javax.ws.rs.client.Invocation;import javax.ws.rs.client.WebTarget;import javax.ws.rs.core.Form;import javax.ws.rs.core.MediaType;import javax.ws.rs.core.Response;
import df.prophete.delire1.model.Prophete;
public class App { public static void main(String[] args) { Client client = ClientBuilder.newClient(); WebTarget delireRestTarget = client.target("http://localhost:8080/delire1/rest/"); WebTarget prophetesTarget = delireRestTarget.path("prophetes"); Invocation.Builder invocationBuilder = prophetesTarget.request(MediaType.APPLICATION_XML); Response response = invocationBuilder.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
WebTarget prophete2Target = prophetesTarget.path("2"); Invocation.Builder invocationBuilder1 = prophete2Target.request(MediaType.APPLICATION_XML); response = invocationBuilder1.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println(); Invocation.Builder invocationBuilder2 = prophetesTarget.request(MediaType.APPLICATION_JSON); response = invocationBuilder2.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
Prophete nouveauProphete = new Prophete("2", "matrix"); WebTarget nouveauPropheteTarget = prophetesTarget.path(nouveauProphete.getId()); Invocation.Builder invocationBuilder3 = nouveauPropheteTarget.request(); response = invocationBuilder3.put(Entity.entity(nouveauProphete, MediaType.APPLICATION_XML)); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); response = invocationBuilder2.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
Invocation.Builder invocationBuilder4 = prophete2Target.request(); response = invocationBuilder4.delete(); System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class)); response = invocationBuilder2.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println();
Form monForm = new Form(); monForm.param("id", "4"); monForm.param("nom", "Vichnou"); Invocation.Builder invocationBuilder5 = prophetesTarget.request(); response = invocationBuilder5.post(Entity.form(monForm)); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); response = invocationBuilder2.get(); System.out.println(response.getStatus()); System.out.println(response.readEntity(String.class)); System.out.println(); }}
Exercices• Au service Prophetes ci-dessus, ajoutez
http://localhost:8080/delire1/rest/prophetes/nombre3
• Au service Prophetes ci-dessus, ajoutez http://localhost:8080/delire1/rest/prophetes/recherche?nom=sus
qui recherche les prophètes dont le nom contient "sus"<links> <href>http://localhost:8080/delire1/rest/prophetes/recherche/3</href></links>
• Améliorez le service Prophetes ci-dessus, en ajoutant les …. prophéties :• chaque prophète aura une liste de prophéties• une prophétie comporte un texte … prophétique et un Id.
• Voici la classe Prophetie :package df.prophete.delire2.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElementpublic class Prophetie { public Prophetie() { super(); this.texte = null; this.id = null; } public Prophetie( String id, String texte) { super(); this.texte = texte; this.id = id; } private String texte; private String id; public String getTexte() { return texte; } public void setTexte(String texte) { this.texte = texte; }
public String getId() { return id; } public void setId( String id) { this.id = id; }}
• et la nouvelle classe Prophete :package df.prophete.delire2.model;
import java.util.HashMap;import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElementpublic class Prophete { public Prophete() { super(); this.nom = null; this.id = null; } public Prophete(Integer id, String nom) { super(); this.nom = nom; this.id = id; } private String nom; private Integer id; private Map<Integer, Prophetie> listePropheties = new HashMap<Integer, Prophetie>(); public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Map<Integer, Prophetie> getListePropheties() { return listePropheties; } public void setListePropheties(Map<Integer, Prophetie> nouvelleListe) { this.listePropheties = nouvelleListe; }}
• et la classe DaoOlympe :package df.prophete.delire2.model;
import java.util.HashMap;
public enum DaoOlympe { instance; private Map<String, Prophete> contentProvider = new HashMap<String, Prophete>(); private DaoOlympe() { Prophete prophete = new Prophete("1", "Zarazoustra"); contentProvider.put(prophete.getId(),prophete); Prophetie prophetie = new Prophetie("1", "Dieu est mort"); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophetie = new Prophetie("2", "Nietzsche est mort aussi"); prophete.getListePropheties().put(prophetie.getId(), prophetie);
prophetie = new Prophetie("3", "vive le surhumain"); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophete = new Prophete("2", "Brian"); contentProvider.put(prophete.getId(),prophete); prophetie = new Prophetie("1", "Le sens de la vie"); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophete = new Prophete("3", "Dac"); contentProvider.put(prophete.getId(),prophete); prophetie = new Prophetie("1", "Si la matière grise était plus rose, le monde aurait moins les idées noires."); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophetie = new Prophetie("2", "Il faut une infinie patience pour attendre toujours ce qui n'arrive jamais."); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophetie = new Prophetie("3", "S'il est bon de ne rien dire avant de parler il est encore plus utile de réfléchir avant de penser."); prophete.getListePropheties().put(prophetie.getId(), prophetie); } public Map<String, Prophete> getListeProphetes(){ return contentProvider; }
}
• Dans la classe PropheteRessource, ajoutez la sub-locator ressource :... @Path("/propheties") public ProphetiesRessource getPropheties() { return new ProphetiesRessource(uriInfo, request, id); }
• Voici le début de la classe ProphetiesRessource :...@Path("/")public class ProphetiesRessource { @Context UriInfo uriInfo; @Context Request request; Integer idProphete; public ProphetiesRessource(UriInfo uriInfo, Request request, Integer idProphete) { super(); this.uriInfo = uriInfo; this.request = request; this.idProphete = idProphete; }...
• Complétez la classe ProphetiesRessource pour satisfaire les requêtes :• GET prophetes/13/propheties donnera la liste des prophéties du 13-ème prophète
• POST prophetes/13/propheties + body comportant un id et un texte créera cette prophétie du 13-ème prophète
• Editez une classe ProphetieRessource pour satisfaire les requêtes :• GET prophetes/13/propheties/666 donnera la 666-iéme prophéties du 13-ème prophète
• DELETE prophetes/13/propheties/666 supprimera la 666-iéme prophétie du 13-ème prophète
• PUT prophetes/13/propheties/666 + body comportant un élément XML correspondant à un prophète mettra à jour la 666-iéme prophétie du 13-ème prophète avec cet item.