REST - UPJV

21
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

Transcript of REST - UPJV

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 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.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>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/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>     <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 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.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 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>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 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="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-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.