Aujourd'hui, j'aimerais emmener le débat sur les services Web REST avec l'implémentation JAX-RS (JSR-311) du framework CXF d'Apache. Pour commencer, je vais rentrer dans le vif du sujet avec un exemple non exhaustif mais significatif, en utilisant l'IDE JDeveloper d'Oracle et le serveur Web Tomcat.
Initialisation de l'environnement de développement
Création d'un projet Web sous JDeveloper
Implémentation de services REST
Courte introduction à REST
Classe de services effectuant une sortie plain/text
- Formulaire pour effectuer un POST
- Formulaire pour effectuer un PUT
- Formulaire pour effectuer un DELETE
Classe de services effectuant une sortie application/xml
Fichier de configuration des services : beans.xml
Fichier de configuration web.xml
Exemple de classe de tests unitaires JUnit allégée
Fichier index.html pour l'application Web "Gestion de ressources"
Déploiement sous Tomcat avec JDeveloper
Utilisation de l'application "Gestion de ressources"
Page d'accueil
Accès à la description WADL
Ajouter une ressource
Lire une ressource
Mettre à jour une ressource
Supprimer une ressource
Conclusion
Initialisation de l'environnement de développement
J'ai choisi de travailler sous Windows XP avec les produits suivants :
- JDK 1.6.0_23
- Tomcat 6.0.32
- JDevelopper 11.1.1.4.0; la version complète d'1,3 Go et non pas la version allégée de 75 Mo.
- CXF 2.3.2
Après avoir téléchargé et installé les produits (dans le répertoire d:\bin par exemple), il faut configurer quelques variables d'environnement système associés. Dans le panneau de configuration, côté "Système", dans l'onglet "Avancé", cliquer sur "Variables d'environnement" et ajouter dans la partie "Variables système" :
- CATALINA_HOME=d:\bin\apache-tomcat-6.0.32
- JAVA_HOME=d:\bin\jdk1.6.0_23
- ;%JAVA_HOME%\bin à la fin de PATH
Création d'un projet Web sous JDeveloper
Pour ce faire, je vous propose de suivre les étapes suivantes :
- dans le menu "File", choisir "New...",
- sélectionner "Generic Application" dans la catégorie "General",
- entrer le nom de l'application et le nom du projet (au sens JDeveloper),
- ajouter une ressource web.xml au projet,
Après cette étape, le système de fichier associé au projet dans le workspace de travail est le suivant :
Il peut être utile ici de créer les répertoires "lib" dans "WEB-INF", "src" dans "myrestprj" et "test" dans "src".
- faire un click droit sur le projet et choisir "Deploy/New Deployment Profile...",
- donner le nom "mywebapp" et valider,
- ajouter le filtre "**/*Test.class" en exclusion dans les filtres de "WEB-INF/classes",
Ce filtre permet d'exclure les classes de tests unitaires dont les noms finissent par "Test", de type JUnit par exemple, du WAR à générer.
- aller ensuite sur "Platform", définir "Tomcat 6.x" comme plateforme par défaut et valider,
Cela permet également d'éviter que JDeveloper ajoute un fichier weblogic.xml par défaut dans le WAR généré.
- dans le menu "View", choisir "Application Server Navigator",
- faire un click droit sur "Application Servers" et choisir "New Application Server...",
- créer un serveur d'application de type Tomcat Standalone,
- intégrer le framework JUnit à JDeveloper (non intégré par défaut), en le téléchargeant via JDeveloper,
- copier dans "WEB-INF\lib" les librairies provenant de CXF suivantes :
commons-logging-1.1.1.jar cxf-2.3.2.jar geronimo-activation_1.1_spec-1.1.jar geronimo-annotation_1.0_spec-1.1.1.jar jsr311-api-1.1.1.jar neethi-2.0.4.jar spring-aop-3.0.5.RELEASE.jar spring-asm-3.0.5.RELEASE.jar spring-beans-3.0.5.RELEASE.jar spring-context-3.0.5.RELEASE.jar spring-core-3.0.5.RELEASE.jar spring-expression-3.0.5.RELEASE.jar spring-web-3.0.5.RELEASE.jar wsdl4j-1.6.2.jar XmlSchema-1.4.7.jar
- faire un click droit sur le projet et choisir "Project Properties...",
- aller dans "Libraries and Classpath",
- cliquer sur "User" et ajouter un nouvel ensemble de librairies utilisateur (portant le nom "mylib" par exemple),
Implémentation de services REST
Courte introduction à REST
CXF permet de réaliser des services REST en utilisant des annotations JAVA, en mappant ces derniers sur les méthodes ou verbes HTTP et en jouant sur les URIs. Les verbes HTTP concernés sont POST, GET, PUT et DELETE. On retrouve ainsi les quatres opérations de base pour la persistance des données, à savoir les fameux CRUD (Create, Read, Update et Delete). Cependant, certains navigateurs ne supportent pas toutes les méthodes HTTP, notamment PUT et DELETE. Du coup CXF vient avec une technique particulière pour adresser ces deux méthodes.
Je vous propose à ce stade d'implémenter une petite application Web de gestion de ressources diverses et variées.
Classe de services effectuant une sortie plain/text
Commençons par créer une classe JAVA "TextOutputServices" dans le package "mycommunityman.myrest", par exemple, dans laquelle figure un attribut statique contenant un petit ensemble de ressources de départ. Les services de cette classe renvoient un résultat sous forme de String. Ils sont de type plain/text. Les quatre verbes HTTP sont implémentés dans cette classe.
package mycommunityman.myrest;
import java.util.HashMap;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/id/{id}")
@Produces(MediaType.TEXT_PLAIN)
public class TextOutputServices {
private static HashMap ressources = new HashMap();
static {
ressources.put("1", "moi");
ressources.put("2", "toi");
ressources.put("3", "lui ou elle");
}
@GET
public String get(@PathParam("id") String id) {
return "C'est " + ressources.get(id) + " !";
}
@POST
@Path("/value/{value}")
public String post(@PathParam("id") String id, @PathParam("value") String name) {
String result;
if (ressources.get(id) == null) {
ressources.put(id, name);
result = "Bonjour " + ressources.get(id) + " !";
} else {
result = "Impossible d'ajouter la ressource. L'identifiant de ressource existe déjà.";
}
return result;
}
@DELETE
public String delete(@PathParam("id") String id) {
return "Au revoir " + ressources.remove(id) + " !";
}
@PUT
@Path("/value/{value}")
public String put(@PathParam("id") String id, @PathParam("value") String name) {
String result;
if (ressources.get(id) != null) {
ressources.put(id, name);
result = "Rebonjour " + ressources.get(id) + " !";
} else {
result = "Impossible de mettre à jour la ressource. La ressource n'existe pas.";
}
return result;
}
}
|
- Formulaire HTML pour effectuer un POST
Pour effectuer un POST de ressource (l'équivalent d'un ajout de ressource), mettons en place un formulaire HTML dédié : "topost.html".
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></meta>
<title>topost</title>
<script type="text/javascript">
function validate() {
document.forms[0].action = document.forms[0].action +
"/id/" + document.forms[0].elements['id'].value +
"/value/" + document.forms[0].elements['value'].value;
document.forms[0].submit();
}
</script>
</head>
<body>
<form action="./myrest/textoutputservices" method="POST">
<input type="text" name="id"/>
<input type="text" name="value"/>
<input type="submit" onclick="validate()"/>
</form>
</body>
</html>
|
Le code JAVASCRIPT permet de changer l'action du formulaire lors de la soumission de ce dernier. L'URI est ainsi adapté par rapport aux données saisies.
- Formulaire HTML pour effectuer un PUT
Pour effectuer un PUT de ressource (l'équivalent d'une mise à jour de ressource), mettons en place, de la même manière que précédemment, un formulaire HTML dédié pour mieux comprendre ce qui se passe : "toput.html".
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></meta>
<title>toput</title>
<script type="text/javascript">
function validate() {
document.forms[0].action = document.forms[0].action +
"/id/" + document.forms[0].elements['id'].value +
"/value/" + document.forms[0].elements['value'].value +
"?_method=put";
document.forms[0].submit();
}
</script>
</head>
<body>
<form action="./myrest/textoutputservices" method="POST">
<input type="text" name="id"/>
<input type="text" name="value"/>
<input type="submit" onclick="validate()"/>
</form>
</body>
</html>
|
Pour simuler l'action PUT, CXF (ou c'est XF d'ailleurs ?) utilise toujours l'action POST, mais passe un paramètre "_method" à la requête dont la valeur doit être "put".
- Formulaire HTML pour effectuer un DELETE
Pour effectuer un DELETE de ressource (l'équivalent d'une suppression de ressource), mettons en place un formulaire HTML dédié également : "todelete.html".
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></meta>
<title>todelete</title>
<script type="text/javascript">
function validate() {
document.forms[0].action = document.forms[0].action +
"/id/" + document.forms[0].elements['id'].value +
"?_method=delete";
document.forms[0].submit();
}
</script>
</head>
<body>
<form action="./myrest/textoutputservices" method="POST">
<input type="text" name="id"/>
<input type="submit" onclick="validate()"/>
</form>
</body>
</html>
|
De la même manière que précédemment, pour simuler l'action DELETE, CXF utilise l'action POST, mais avec un paramètre de requête "_method" dont la valeur doit être "delete".
Classe de services effectuant une sortie application/xml
Commençons par créer une classe POJO "EmbeddedInformation" dans le package "mycommunityman.mypojo" par exemple. Elle va servir de classe résultat renvoyée par les services REST.
package mycommunityman.mypojo;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "embeddedInformation")
public class EmbeddedInformation {
private String information;
private EmbeddedInformation moreInformation;
…
}
|
L'annotation "@XmlRootElement" permet d'indiquer que la classe sera traduite en élément XML racine dans un document XML.
Puis, implémentons des services REST en exploitant uniquement le verbe GET pour simplifier. Pour cela, créons la classe "XmlOutputServices" dans le package "mycommunityman.myrest".
package mycommunityman.myrest;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import mycommunityman.mypojo.EmbeddedInformation;
@Path("/name/{name}")
@Produces(MediaType.APPLICATION_XML)
public class XmlOutputServices {
@GET
public EmbeddedInformation get(@PathParam("name") String name) {
return new EmbeddedInformation("Bonjour " + name + " !", null);
}
@GET
@Path("/something/{something}")
public EmbeddedInformation get(@PathParam("name") String name,
@PathParam("something") String something) {
EmbeddedInformation result = get(name);
result.setMoreInformation(new EmbeddedInformation("Aimez-vous " + something + " ?", null));
return result;
}
@GET
@Path("/something/{something}/{params:.*}")
public EmbeddedInformation get(@PathParam("name") String name,
@PathParam("something") String something,
@PathParam("params") List
EmbeddedInformation result = get(name, something);
EmbeddedInformation tmp = build(paramsAsList);
result.getMoreInformation().setMoreInformation(new EmbeddedInformation("Mais encore ?", tmp));
return result;
}
private EmbeddedInformation build(List paramsAsList) {
EmbeddedInformation result = new EmbeddedInformation();
EmbeddedInformation resultTmp = result;
int i = 0;
for(; i < paramsAsList.size()-1; i++) {
String param = paramsAsList.get(i).toString();
resultTmp.setInformation(param);
resultTmp.setMoreInformation(new EmbeddedInformation());
resultTmp = resultTmp.getMoreInformation();
}
resultTmp.setInformation(paramsAsList.get(i).toString());
return result;
}
}
|
Fichier de configuration des services : beans.xml
Le framework CXF repose sur le framework Spring pour sa partie configuration des services dans une application Web. Le fichier beans.xml de Spring est défini de la manière suivante :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxrs:server id="textoutputservices" address="/textoutputservices">
<jaxrs:serviceBeans>
<ref bean="textOutputServices" />
</jaxrs:serviceBeans>
</jaxrs:server>
<bean id="textOutputServices" class="mycommunityman.myrest.TextOutputServices" />
<jaxrs:server id="xmloutputservices" address="/xmloutputservices">
<jaxrs:serviceBeans>
<ref bean="xmlOutputServices" />
</jaxrs:serviceBeans>
</jaxrs:server>
<bean id="xmlOutputServices" class="mycommunityman.myrest.XmlOutputServices" />
</beans>
|
Fichier de configuration web.xml
Le fichier de configuration de l'application Web "Gestion de ressources" est le suivant :
<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/myrest/*</url-pattern>
</servlet-mapping>
</web-app>
|
Exemple de classe de tests unitaires JUnit allégée
Pour créer une classe de tests "TextOutputServicesTest", faisons un click droit sur le package "test", choisissons "New...", et sélectionnons "Unit Tests" dans la catégorie "General". La classe de tests unitaires JUnit de la classe "TextOutputServices" pourrait s'écrire de la manière suivante :
package test.mycommunityman.myrest;
import mycommunityman.myrest.TextOutputServices;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class TextOutputServicesTest{
public TextOutputServicesTest () {
}
/**
* @see TextOutputServices#get(String)
*/
@Test
public void testGet() {
TextOutputServices textOutputServices = new TextOutputServices();
assertEquals("C'est moi !", textOutputServices.get("1"));
assertEquals("C'est toi !", textOutputServices.get("2"));
assertEquals("C'est lui ou elle !", textOutputServices.get("3"));
}
}
|
L'exécution des tests donne le résultat suivant :
Fichier index.html pour l'application Web "Gestion de ressources"
Pour donner accès, de manière explicite, aux différents services de notre petite application, je vous propose de mettre en place le fichier "index.html".
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></meta>
<title>index</title>
</head>
<body>
Bienvenue sur le site de gestion de ressources !
<br>
<br>
<a href="./myrest">accéder aux services REST</a>
<br>
<a href="./topost.html">ajouter une ressouce</a>
<br>
<a href="./todelete.html">supprimer une ressouce</a>
<br>
<a href="./toput.html">mettre à jour une ressouce</a>
</body>
</html>
|
Après cette étape, la structure du projet sous JDeveloper est la suivante :
Déploiement sous Tomcat avec JDeveloper
- En préambule, nous pouvons nous amuser à configurer Tomcat afin d'avoir accès à la console d'administration. Pour cela, il faut éditer le fichier %CATALINA_HOME%\conf\tomcat-users.xml et ajouter un utilisateur de la manière suivante :
<role rolename="manager-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui"/>
|
La console d'administration Tomcat est accessible à l'URL http://localhost:8080/. En cliquant sur "Tomcat Manager" dans le menu en haut à gauche, une popup d'authentification apparaît. Il faut alors saisir le login et le mot de passe vus ci-dessus.
On accède alors à la partie administration.
- Démarrons le serveur Tomcat, en double cliquant par exemple sur le script %CATALINA_HOME%\bin\startup.bat,
- Faisons un click droit sur le projet, choisissons "Deploy" et puis "mywebapp...",
Le fichier "mywebapp.war" est copié dans le répertoire %CATALINA_HOME%\webapps. Et l'application est déployée à chaud et est accessible à l'URL http://localhost:8080/mywebapp.
Utilisation de l'application "Gestion de ressources"
Page d'accueil
La page d'accueil de notre petite application Web de gestion de ressources ressemble à :
Accès à la description WADL
Une description WADL (Web Application Description Language) est le pendant d'une description WSDL (Web Service Description Language) des services SOAP pour les services RESTful. Ce fichier descriptif des services REST repose sur une grammaire beaucoup plus simple que celle du WSDL. Pour notre application, les ressources WADL sont disponibles à l'URL http://localhost:8080/mywebapp/myrest.
Ajouter une ressource
Vérifions que la ressource à ajouter n'existe pas déjà.
Ajoutons la nouvelle ressource.
Vérifions qu'elle a bien été ajoutée.
Lire une ressource
Pour utiliser la méthode HTTP GET, il suffit simplement de saisir l'URI de la ressource recherchée directement dans le navigateur. Pour récupérer la ressource dont l'id est 1 via le service renvoyant une réponse plain/text, il suffit de saisir http://localhost:8080/mywebapp/myrest/textoutputservices/id/1.
Pour utiliser un des services renvoyant une réponse application/xml, nous pouvons utiliser http://localhost:8080/mywebapp/myrest/xmloutputservices/name/mycommunityman/something/la pomme/la cerise/et/l'orange.
Mettre à jour une ressource
Avant de mettre à jour la ressource, récupérons sa valeur.
Mettons à jours la ressource maintenant.
Vérifions qu'elle a bien été mise à jour.
Supprimer une ressource
Commençons par récupérer la ressource à supprimer.
Supprimons cette ressource.
Vérifions qu'elle a bien été supprimée.
Conclusion
Malgré la longueur de cet article, nous sentons bien la facilité avec laquelle nous pouvons écrire des services RESTful, que l'on soit en point à point ou en mode orchestration. REST est bien adapté aux applications Web de type gestion de ressources, en offrant un style d'architecture existante (l'organisation de toutes les ressources sur internet) et adéquate (on peut presque tout faire rien qu'avec les méthodes HTTP GET et POST).
Contrairement à l'orienté-objet, REST se base sur les URI plutôt que sur les méthodes. De plus, il n'y a plus la complexité des enveloppes SOAP (Simple Object Access Protocol) ou des protocoles RPC (Remote Procedure Call). Bien évidemment, les avantages et les inconvénients ne peuvent pas se résumer qu'à ces quelques lignes.Alors, à vos REST, prêt, partez !
Aucun commentaire:
Enregistrer un commentaire