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 du 13-è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 Content­Type :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.html documentation 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/kepler Cochez -> 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/releases Cochez → 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>jersey­container­servlet</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 web­app PUBLIC "­//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web­app_2_3.dtd" > <web­app> <display­name>Hello</display­name> <servlet> <servlet­name>Hello</servlet­name> <servlet­class>org.glassfish.jersey.servlet.ServletContainer</servlet­class> <init­param> <param­name>jersey.config.server.provider.packages</param­name> <param­value>df.jersey.helloserver</param­value> </init­param> <load­on­startup>1</load­on­startup> </servlet> <servlet­mapping> <servlet­name>Hello</servlet­name> <url­pattern>/rest/*</url­pattern> </servlet­mapping> </web­app> • • • → 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/bonjour bonjour 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> <servlet­name>Hello</servlet­name> <servlet­class>org.glassfish.jersey.servlet.ServletContainer</servlet­class> <init­param> <param­name>jersey.config.server.provider.packages</param­name> <param­value>df.jersey.helloserver</param­value> </init­param> <load­on­startup>1</load­on­startup> </servlet> <servlet­mapping> <servlet­name>Hello</servlet­name> <url­pattern>/rest/*</url­pattern> </servlet­mapping> </web­app> • • • • • 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 servletmapping 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.rs­api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey­client</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 valeur bilou 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 valeur bilou au paramètre nom de la méthode exécutée. Les types JAVA qui peuvent être passés : 1. Type primitif 2. String 3. Type/Classe ayant un constructeur qui a un seul argument String 4. 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=bibi et 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>jersey­client</artifactId> <version>2.8</version> </dependency> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey­container­servlet</artifactId> <version>2.8</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey­media­moxy</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/delire­0"); 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="UTF­8" standalone="yes"?> <application xmlns="http://wadl.dev.java.net/2009/02"> <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.8 2014­04­29 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/delire­0/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/delire­0/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/java­7­openjdk­amd64 ; export JAVA_HOME ./wadl­dist­1.1.6/bin/wadl2java ­o . ­p df.delire0.wadl.client http://localhost:8080/delire­0/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.rs­api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey­client</artifactId> <version>2.9</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey­client</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 ProphetesResource package 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="UTF­8"> <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-contre produit 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/nombre 3 • 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; @XmlRootElement public 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; @XmlRootElement public 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.