Architecture des SI - partie 2

publicité
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.
Téléchargement