Prérequis logiciels▲
Avant de démarrer cette série d'exercices sur l'utilisation de l'API JAX-WS, veuillez préparer votre environnement de développement en installant les outils suivants :
I. Exercice 1 : développer un service web REST « Bonjour ENSMA »▲
I-A. But▲
- Développer un service web REST à partir d'une classe Java.
- Déployer le service web REST comme une application Java classique.
- Afficher le contrat de description WADL.
- Tester le service web REST avec cURL.
I-B. Description▲
Le service web REST de ce premier exercice fournit un accès à la ressource « Hello » qui permet de retourner des messages de bienvenue. Le service proposé n'autorise que la lecture à la ressource (GET) et des paramètres peuvent être autorisés. Le format supporté par la ressource « Hello » sera du texte (text/plain).
I-C. Étapes à suivre▲
- Démarrer l'environnement de développement Eclipse.
- Importer le projet Maven jaxrs-tutorial-exercice1 (File -> Import -> Maven -> Existing Maven Projects), choisir le répertoire du projet, puis faire Finish.
- Créer une classe qui représentera la ressource « Hello » (File -> New puis choisir Class). Appeler la classe HelloResource et la définir dans le package fr.mickaelbaron.jaxrstutorialexercice1.
- Dans la nouvelle classe créée, ajouter l'annotation @Path("hello") pour préciser que la ressource sera accessible via le chemin /hello et l'annotation @Produces(MediaType.TEXT_PLAIN) pour indiquer que le contenu retourné au client sera de type texte (text/plain).
2.
3.
4.
5.
@Path
(
"hello"
)
@Produces
(
MediaType.TEXT_PLAIN)
public
class
HelloResource {
public
HelloResource
(
) {
}
}
- Ajouter une première méthode String getHello() permettant de retourner une constante de type chaîne de caractères Bonjour ENSMA (ou autre texte de votre création).
2.
3.
4.
@GET
public
String getHello
(
) {
return
"Bonjour ENSMA"
;
}
- Afin de résoudre les problèmes de dépendances vers la bibliothèque JAX-RS, compléter le fichier de description Maven pom.xml en ajoutant la dépendance suivante (balise <dependencies>).
2.
3.
4.
5.
<dependency>
<groupId>
javax.ws.rs</groupId>
<artifactId>
javax.ws.rs-api</artifactId>
<version>
${jaxrs.version}</version>
</dependency>
Cette dépendance (API de JAX-RS) ne sert qu'à résoudre les problèmes de visibilité des annotations. Nous aurons besoin de dépendances supplémentaires (implémentation de JAX-RS) quand nous souhaiterons déployer notre service web REST.
- Compléter la classe HelloLauncher dans le package fr.mickaelbaron.jaxrstutorialexercice1. Cette classe sera utilisée pour publier localement notre service web REST.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
public
class
HelloLauncher {
public
static
final
URI BASE_URI =
getBaseURI
(
);
private
static
URI getBaseURI
(
) {
return
UriBuilder.fromUri
(
"http://localhost/api/"
).port
(
9991
).build
(
);
}
public
static
void
main
(
String[] args) {
ResourceConfig rc =
new
ResourceConfig
(
);
rc.registerClasses
(
HelloResource.class
);
rc.property
(
LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName
(
));
try
{
HttpServer server =
GrizzlyHttpServerFactory.createHttpServer
(
BASE_URI, rc);
server.start
(
);
System.out.println
(
String.format
(
"Jersey app started with WADL available at "
+
"%sapplication.wadl
\n
Hit enter to stop it..."
,
BASE_URI, BASE_URI));
System.in.read
(
);
server.shutdownNow
(
);
}
catch
(
Exception e) {
e.printStackTrace
(
);
}
}
}
- Afin de résoudre les problèmes de dépendances vers le serveur Grizzly, compléter le fichier de description Maven pom.xml.
2.
3.
4.
<dependency>
<groupId>
org.glassfish.jersey.containers</groupId>
<artifactId>
jersey-container-grizzly2-http</artifactId>
</dependency>
- Exécuter la classe HelloLauncher.
- Ouvrir une fenêtre d'un navigateur web et tester la récupération de la ressource « Hello » (requête GET via l'URL http://localhost:9991/api/hello).
Ce service web REST n'est pas complet puisqu'il n'est pas possible de paramétrer le message de bienvenue (utilisation de template parameter) ni de connaître l'auteur du message de bienvenue (utilisation d'un paramètre d'en-tête).
- Ajouter une nouvelle méthode Java getHello dans la classe HelloResource qui prend en paramètre une chaîne de caractères initialisée par un template parameter (annotations @Path et @PathParam) et une autre chaîne de caractères initialisée par un paramètre d'en-tête (annotation @HeaderParam) dont la clé sera name. La valeur par défaut de l'en-tête sera fixée votre serviteur (annotation @DefaultValue).
2.
3.
4.
5.
6.
@GET
@Path
(
"{id}"
)
public
String getHello
(
@PathParam
(
"id"
) String id,
@DefaultValue
(
"votre serviteur"
) @HeaderParam
(
"name"
) String name) {
return
"Bonjour "
+
id +
" de la part de "
+
name;
}
- Exécuter de nouveau la classe HelloLauncher et depuis votre navigateur web saisir l'URL permettant d'invoquer ce nouveau service web REST (requête GET via l'URL http://localhost:9991/api/hello/ENSMA). Malheureusement, le navigateur web ne permet pas de préciser la valeur du paramètre d'en-tête name. Nous utiliserons donc l'outil en ligne de commande cURL pour construire des requêtes HTTP complexes.
- Depuis une invite de commande saisir la commande suivante :
2.
$ curl --header "name:Mickael BARON" http://localhost:9991/api/hello/ENSMA
Bonjour ENSMA de la part de Mickael BARON
Ce service web REST n'est toujours pas complet, puisque nous aimerions retourner dans l'en-tête de la réponse, l'auteur du message de bienvenue. Comment pourrions-nous retourner à la fois un contenu dans la réponse et une information dans l'en-tête de la réponse ? Pour cela, nous allons utiliser un objet Response pour le retour de méthode.
- Ajouter une nouvelle méthode Java getHelloWithHeaders dans la classe HelloENSMA qui possède les mêmes paramètres que la précédente méthode. Le chemin pour invoquer cette méthode sera withheaders/{id} où id est le paramètre du message de bienvenue. Dans le corps de la méthode getHelloWithHeaders, compléter le code ci-dessous afin de transmettre le nom de l'auteur dans l'en-tête de la réponse.
2.
3.
4.
5.
6.
@GET
@Path
(
"withheaders/{id}"
)
public
Response getHelloWithHeaders
(
@PathParam
(
"id"
) String id,
@DefaultValue
(
"votre serviteur"
) @HeaderParam
(
"name"
) String name) {
return
Response.ok
(
).header
(
"name"
, name).entity
(
"Bonjour "
+
id +
" de la part de (voir l'en-tête)."
).build
(
);
}
- Exécuter de nouveau la classe HelloLauncher, puis saisir la ligne de commande cURL suivante pour envoyer une requête avec les bons paramètres et détailler le retour de la réponse.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
$ curl --header "name:Mickael BARON" http://localhost:9991/api/hello/withheaders/ENSMA -v
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 9991 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9991 (#0)
> GET /api/hello/withheaders/ENSMA HTTP/1.1
> Host: localhost:9991
> User-Agent: curl/7.54.0
> Accept: */*
> name:Mickael BARON
>
< HTTP/1.1 200 OK
< name: Mickael BARON
< Content-Type: text/plain
< Content-Length: 46
<
* Connection #0 to host localhost left intact
Bonjour ENSMA de la part de (voir l'en-tête).
- Nous allons afficher le contrat de description de ce service web REST au format WADL. Saisir depuis un navigateur l'URL suivante : http://localhost:9991/api/application.wadl.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
<?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.27 2018-04-10 07:34:57"
/>
<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:9991/api/application.wadl?detail=true"
/>
<grammars/>
<resources
base
=
"http://localhost:9991/api/"
>
<resource
path
=
"hello"
>
<method
id
=
"getHello"
name
=
"GET"
>
<response>
<representation
mediaType
=
"text/plain"
/>
</response>
</method>
<resource
path
=
"{id}"
>
<param
xmlns
:
xs
=
"http://www.w3.org/2001/XMLSchema"
name
=
"id"
style
=
"template"
type
=
"xs:string"
/>
<method
id
=
"getHello"
name
=
"GET"
>
<request>
<param
xmlns
:
xs
=
"http://www.w3.org/2001/XMLSchema"
name
=
"name"
style
=
"header"
type
=
"xs:string"
default
=
"votre serviteur"
/>
</request>
<response>
<representation
mediaType
=
"text/plain"
/>
</response>
</method>
</resource>
<resource
path
=
"withheaders/{id}"
>
<param
xmlns
:
xs
=
"http://www.w3.org/2001/XMLSchema"
name
=
"id"
style
=
"template"
type
=
"xs:string"
/>
<method
id
=
"getHelloWithHeaders"
name
=
"GET"
>
<request>
<param
xmlns
:
xs
=
"http://www.w3.org/2001/XMLSchema"
name
=
"name"
style
=
"header"
type
=
"xs:string"
default
=
"votre serviteur"
/>
</request>
<response>
<representation
mediaType
=
"text/plain"
/>
</response>
</method>
</resource>
</resource>
</resources>
</application>
II. Exercice 2 : développer un service web REST « Interrogation et réservation de billets de train »▲
II-A. But▲
- Développer un service web REST à partir de classes Java.
- Utiliser un Sub-Resource Locator.
- Manipuler des types personnalisés.
- Manipuler des formats de représentations (XML et JSON).
II-B. Description▲
Le service web REST de ce deuxième exercice consiste à créer un système CRUD pour l'interrogation et la réservation de billets de train. Les ressources manipulées par ce service web REST sont donc un train et une réservation de billets de train pour un train donné. Le service web REST doit pouvoir lister l'ensemble des trains, lister les trains qui satisfont un critère de recherche (ville de départ, ville d'arrivée, jour de départ et un intervalle de temps), puis de créer, lister et supprimer une réservation de billets de train.
Nous insisterons sur la mise en place du service web REST et non sur le code métier (le code Java dans le corps des méthodes est « sans importance »). Les formats supportés par les deux ressources seront du XML et du JSON.
II-C. Étapes à suivre▲
- Démarrer l'environnement de développement Eclipse.
- Importer le projet Maven jaxrs-tutorial-exercice2 (File -> Import -> Maven -> Existing Maven Projects), choisir le répertoire du projet, puis faire Finish.
Le projet importé contient déjà des classes. À ce stade, de nombreuses erreurs de compilation sont présentes, dues à l'absence de certaines classes Java. Pas d'inquiétude, nous allons les ajouter progressivement.
- Créer une classe Train (dans le package fr.mickaelbaron.jaxrstutorialexercice2) qui modélise le concept de train et qui contient un attribut String id (identifiant fonctionnel d'un train), un attribut String departure (la ville de départ du train), un attribut String arrival (la ville d'arrivée du train) et un attribut int departureTime (heure de départ). Ajouter des modificateurs et des accesseurs sur tous les attributs. Ci-dessous un résultat du code que vous devez obtenir.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
package
fr.mickaelbaron.jaxrstutorialexercice2;
public
class
Train {
private
String id;
private
String departure;
private
String arrival;
private
int
departureTime; // Format : 1230 = 12h30
public
Train
(
) {
}
public
Train
(
String pId, String departure, String arrival, int
departureTime) {
this
.id =
pId;
this
.departure =
departure;
this
.arrival =
arrival;
this
.departureTime =
departureTime;
}
public
String getId
(
) {
return
id;
}
public
void
setId
(
String pId) {
this
.id =
pId;
}
public
String getDeparture
(
) {
return
departure;
}
public
void
setDeparture
(
String departure) {
this
.departure =
departure;
}
public
String getArrival
(
) {
return
arrival;
}
public
void
setArrival
(
String arrival) {
this
.arrival =
arrival;
}
public
int
getDepartureTime
(
) {
return
departureTime;
}
public
void
setDepartureTime
(
int
departureTime) {
this
.departureTime =
departureTime;
}
}
- Examiner la classe TrainBookingBD qui joue le rôle de DAO et de base de données. En effet, toutes les instances créées se feront en mémoire.
- Créer la classe TrainResource (dans le package fr.mickaelbaron.jaxrstutorialexercice2) permettant de représenter la ressource train. La classe contient trois méthodes qui permettent respectivement : 1) de retourner une liste de tous les trains ; 2) d'obtenir un train par son identifiant ; 3) de rechercher un train par des critères passés en paramètres (ville de départ, ville d'arrivée et heure de départ). Noter que les formats de retour peuvent être du XML ou du JSON. Compléter la classe ci-dessous en remplaçant TODO par les bonnes instructions JAX-RS.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
package
fr.mickaelbaron.jaxrstutorialexercice2;
// TODO: le chemin racine de la ressource doit débuter par `/trains`.
// TODO: les formats du contenu des réponses sont XML puis JSON.
public
class
TrainResource {
public
TrainResource
(
) {
}
// TODO: préciser le verbe HTTP.
public
List<
Train>
getTrains
(
) {
System.out.println
(
"getTrains"
);
return
TrainBookingDB.getTrains
(
);
}
// TODO: préciser le verbe HTTP
// TODO: le chemin doit commencer par `/trainid-` et se finir
// par un template paramètre désignant l'identifiant du train.
public
Train getTrain
(
String trainId) {
System.out.println
(
"getTrain"
);
Optional<
Train>
trainById =
TrainBookingDB.getTrainById
(
trainId);
if
(
trainById.isPresent
(
)) {
return
trainById.get
(
);
}
else
{
// TODO: déclencher une exception avec un statut NOT_FOUND.
}
}
// TODO: préciser le verbe HTTP.
// TODO: le chemin doit commencer par `/search`.
// Les paramètres sont tous des paramètres de requête.
public
??? searchTrainsByCriteria
(
String departure, String arrival, String departureTime) {
System.out.println
(
"TrainResource.searchTrainsByCriteria()"
);
// TODO: retourner une réponse avec :
// 1/ les trois paramètres de requête en en-tête
// 2/ un sous-ensemble de la liste des trains
// (exemple : `TrainBookingDB.getTrains().subList(0, 2)`)
}
}
- Créer la classe TrainBookingLauncher utilisée pour tester ce service web REST.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
package
fr.mickaelbaron.jaxrstutorialexercice2;
public
class
TrainBookingLauncher {
public
static
final
URI BASE_URI =
getBaseURI
(
);
private
static
URI getBaseURI
(
) {
return
UriBuilder.fromUri
(
"http://localhost/api/"
).port
(
9992
).build
(
);
}
public
static
void
main
(
String[] args) {
ResourceConfig rc =
new
ResourceConfig
(
);
rc.registerClasses
(
TrainResource.class
);
rc.property
(
LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName
(
));
try
{
HttpServer server =
GrizzlyHttpServerFactory.createHttpServer
(
BASE_URI, rc);
server.start
(
);
System.out.println
(
String.format
(
"Jersey app started with WADL available at "
+
"%sapplication.wadl
\n
Hit enter to stop it..."
, BASE_URI, BASE_URI));
System.in.read
(
);
server.shutdownNow
(
);
}
catch
(
Exception e) {
e.printStackTrace
(
);
}
}
}
- Exécuter la classe TrainBookingLauncher, puis à partir de cURL invoquer le service web permettant de récupérer la liste de tous les trains.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$ curl http://localhost:9992/api/trains -v
* Connected to localhost (127.0.0.1) port 9992 (#0)
> GET /api/trains HTTP/1.1
> Host: localhost:9992
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: close
< Content-Length: 0
<
* Closing connection 0
Comme observé sur le retour de la commande cURL, une erreur 500 (*Internal Server Error*) est retournée. Sur la console du serveur, le message suivant a dû être généré.
2.
nov. 06, 2018 6:29:44 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=application/xml, type=class java.util.ArrayList, genericType=java.util.List<fr.mickaelbaron.jaxrstutorialexercice2.Train>.
Cette erreur indique que la classe Train ne contient pas les informations nécessaires pour transformer un objet en XML (XML est le format choisi dans l'ordre d'apparition de l'annotation @Produces). Pour une sérialisation Java <=> XML, chaque classe doit être au moins annotée à la racine pour activer le mapping entre le XML Schema et les attributs de la classe.
- Éditer la classe Train et ajouter l'annotation suivante.
2.
3.
4.
@XmlRootElement
(
name =
"train"
)
public
class
Train {
...
}
- Exécuter de nouveau la classe TrainBookingLauncher, puis à partir de cURL invoquer le service web permettant de récupérer la liste de tous les trains. Comme montré ci-dessous, la liste des trains est obtenue via un format XML.
2.
$ curl http://localhost:9992/api/trains
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><trains><train><arrival>Paris</arrival><departure>Poitiers</departure><departureTime>1250</departureTime><id>TR123</id></train><train><arrival>Paris</arrival><departure>Poitiers</departure><departureTime>1420</departureTime><id>AX127</id></train><train><arrival>Paris</arrival><departure>Poitiers</departure><departureTime>1710</departureTime><id>PT911</id></train></trains>
Nous souhaitons désormais obtenir un retour du contenu au format JSON. Cette information doit être précisée dans l'en-tête de la requête avec l'attribut Accept.
- Saisir la ligne de commande suivante.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$ curl --header "Accept:application/json" http://localhost:9992/api/trains -v
* Connected to localhost (127.0.0.1) port 9992 (#0)
> GET /api/trains HTTP/1.1
> Host: localhost:9992
> User-Agent: curl/7.54.0
> Accept: application/json
>
< HTTP/1.1 500 Internal Server Error
< Connection: close
< Content-Length: 0
<
* Closing connection 0
Comme observé sur le retour de la commande cURL, une erreur 500 est retournée. Sur la console du serveur, le message est identique à la précédente erreur pour le format XML. En d'autres termes, Jersey ne sait pas comment transformer un objet Java en JSON. Pour résoudre cette absence, il suffit d'ajouter la dépendance de la bibliothèque Jackson au fichier pom.xml.
2.
nov. 06, 2018 7:30:14 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List<fr.mickaelbaron.jaxrstutorialexercice2.Train>.
- Éditer le fichier _pom.xml_ et ajouter la dépendance suivante.
2.
3.
4.
<dependency>
<groupId>
org.glassfish.jersey.media</groupId>
<artifactId>
jersey-media-json-jackson</artifactId>
</dependency>
- Exécuter de nouveau la classe TrainBookingLauncher, puis exécuter de nouveau la commande cURL précédente.
2.
$ curl --header "Accept: application/json" http://localhost:9992/api/trains
[{"id":"TR123","departure":"Poitiers","arrival":"Paris","departureTime":1250},{"id":"AX127","departure":"Poitiers","arrival":"Paris","departureTime":1420},{"id":"PT911","departure":"Poitiers","arrival":"Paris","departureTime":1710}]
Vous constatez sur le résultat JSON que le nom des clés correspond exactement au nom des attributs de la classe Java Train.
- Modifier la classe Train afin d'obtenir une clé departure_time (dans le format JSON), au lieu de departureTime, tout en respectant les conventions de nommage Java.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@XmlRootElement
(
name =
"train"
)
public
class
Train {
private
String id;
private
String departure;
private
String arrival;
@JsonProperty
(
"departure_time"
)
private
int
departureTime; // Format : 1230 = 12h30
...
}
- Exécuter la classe TrainBookingLauncher, puis vérifier que le nom de la clé departure_time a été impacté.
2.
$ curl --header "Accept: application/json" http://localhost:9992/api/trains
[{"id":"TR123","departure":"Poitiers","arrival":"Paris","departure_time":1250},{"id":"AX127","departure":"Poitiers","arrival":"Paris","departure_time":1420},{"id":"PT911","departure":"Poitiers","arrival":"Paris","departure_time":1710}]
- Continuer à tester le service web REST de façon à invoquer les méthodes Java getTrain et searchTrainsByCriteria. Pour cette dernière méthode, afficher la réponse complète pour s'assurer que les trois paramètres de requête sont transmis dans l'en-tête de la réponse.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
$ curl --header "Accept: application/json" http://localhost:9992/api/trains/trainid-TR123
{"id":"TR123","departure":"Poitiers","arrival":"Paris","departure_time":1250}
$ curl --header "Accept: application/json" http://localhost:9992/api/trains/search\?departure\=poitiers\&arrival\=paris\&departure_time\=1050 -v
* Connected to localhost (127.0.0.1) port 9992 (#0)
> GET /api/trains/search?departure=poitiers&arrival=paris&departure_time=1050 HTTP/1.1
> Host: localhost:9992
> User-Agent: curl/7.54.0
> Accept: application/json
>
< HTTP/1.1 200 OK
< departure: poitiers
< arrival: paris
< arrivalhour: 1050
< Content-Type: application/json
< Content-Length: 157
<
* Connection #0 to host localhost left intact
[{"id":"TR123","departure":"Poitiers","arrival":"Paris","departure_time":1250},{"id":"AX127","departure":"Poitiers","arrival":"Paris","departure_time":1420}]
Nous allons maintenant nous occuper à implémenter le service dédié à la **réservation de billets de train** pour un train donné. La classe TrainBooking utilisée pour modéliser une réservation de billets est déjà présente dans le projet. Elle contient un attribut String id (identifiant fonctionnel d'une réservation de billets), un attribut String trainId (la clé étrangère du train) et un attribut int numberPlaces pour le nombre de places à réserver. Toutefois, le paramétrage de la classe BookTrainResource est utilisé pour implémenter le service de réservation de billets.
- Modifier la classe TrainBooking afin d'obtenir une clé current_train (dans le format JSON) au lieu de trainId et une clé number_places (dans le format JSON) au lieu de numberPlaces.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
@XmlRootElement
(
name =
"trainbooking"
)
public
class
TrainBooking {
private
String id;
// TODO: le nom de la clé JSON doit être current_train.
private
String trainId;
// TODO: le nom de la clé JSON doit être number_places.
private
int
numberPlaces;
...
}
- Créer la classe BookTrainResource (dans le package fr.mickaelbaron.jaxrstutorialexercice2). Quatre méthodes sont à définir. La première createTrainBooking est invoquée pour la création d'une réservation de billets. La deuxième getTrainBookings est utilisée pour lister l'ensemble des réservations. La troisième getTrainBooking permet de retourner les informations d'une réservation à partir d'un numéro de réservation. Finalement removeBookTrain permet de supprimer une réservation.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
package
fr.mickaelbaron.jaxrstutorialexercice2;
public
class
TrainBookingResource {
// TODO: préciser le verbe HTTP.
public
TrainBooking createTrainBooking
(
TrainBooking trainBooking) {
System.out.println
(
"TrainBookingResource.createTrainBooking()"
);
Optional<
Train>
findFirst =
TrainBookingDB.getTrainById
(
trainBooking.getTrainId
(
));
if
(
findFirst.isEmpty
(
)) {
// TODO: déclencher une exception avec un statut NOT_FOUND.
}
TrainBooking newBookTrain =
new
TrainBooking
(
);
newBookTrain.setNumberPlaces
(
trainBooking.getNumberPlaces
(
));
newBookTrain.setTrainId
(
findFirst.get
(
).getId
(
));
newBookTrain.setId
(
Long.toString
(
System.currentTimeMillis
(
)));
TrainBookingDB.getTrainBookings
(
).add
(
newBookTrain);
return
newBookTrain;
}
// TODO: préciser le verbe HTTP.
public
List<
TrainBooking>
getTrainBookings
(
) {
System.out.println
(
"TrainBookingResource.getTrainBookings()"
);
return
TrainBookingDB.getTrainBookings
(
);
}
// TODO: préciser le verbe HTTP.
// TODO: template paramètre désignant l'identifiant du train.
public
TrainBooking getTrainBooking
(
String trainBookingId) {
System.out.println
(
"TrainBookingResource.getTrainBooking()"
);
Optional<
TrainBooking>
findFirst =
TrainBookingDB.getTrainBookingById
(
trainBookingId);
if
(
findFirst.isPresent
(
)) {
return
findFirst.get
(
);
}
else
{
// TODO: déclencher une exception avec un statut NOT_FOUND.
}
}
// TODO: préciser le verbe HTTP.
// TODO: template paramètre désignant l'identifiant du train.
public
void
removeTrainBooking
(
@PathParam
(
"id"
) String trainBookingId) {
System.out.println
(
"TrainBookingResource.removeTrainBooking()"
);
Optional<
TrainBooking>
findFirst =
TrainBookingDB.getTrainBookingById
(
trainBookingId);
if
(
findFirst.isPresent
(
)) {
TrainBookingDB.getTrainBookings
(
).remove
(
findFirst.get
(
));
}
else
{
// TODO: déclencher une exception avec un statut NOT_FOUND.
}
}
}
Cette ressource définie par la classe TrainBookingResource est accessible via la mise en place d'un sub-resource locator depuis la ressource train. L'avantage est de pouvoir lier la ressource train avec la ressource de réservation de billets.
- Compléter la classe TrainResource de façon à ajouter une méthode getTrainBookingResource qui servira de sub-resource locator. Assurer que les exigences suivantes soient respectées : annotée avec @Path, non annotée avec les annotations de méthodes HTTP (@GET, @POST, @PUT et @DELETE) et doit retourner un objet de type ressource.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
...
public
class
TrainResource {
...
// TODO: la sous-ressource doit être accessible via l'URI `/bookings`
public
??? getTrainBookingResource
(
) {
System.out.println
(
"TrainResource.getTrainBookingResource()"
);
// TODO: doit retourner un objet du type de la ressource souhaitée => TrainBookingResource
}
}
- Exécuter la classe TrainBookingLauncher et à partir de cURL invoquer chaque service lié à la réservation de billets de train qui ont été implémentés dans les quatre méthodes createTrainBooking, getTrainBookings, getTrainBooking et removeTrainBooking.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
# Récupérer la liste des trains.
$ curl --header "Accept: application/json" http://localhost:9992/api/trains
[{"id":"TR123","departure":"Poitiers","arrival":"Paris","departure_time":1250},{"id":"AX127","departure":"Poitiers","arrival":"Paris","departure_time":1420},{"id":"PT911","departure":"Poitiers","arrival":"Paris","departure_time":1710}]
# Créer une réservation de billets de train.
$ curl --header "Accept: application/json" --header "Content-Type: application/json" --request POST --data '{"current_train":"TR123","number_places":2}' http://localhost:9992/api/trains/bookings
{"id":"1541683057395","current_train":"TR123","number_places":2}
# Récupérer la liste des réservations de billets de train.
$ curl --header "Accept: application/json" http://localhost:9992/api/trains/bookings
[{"id":"1541683057395","current_train":"TR123","number_places":2}]
# Récupérer une réservation de billets de train par un identifiant.
$ curl --header "Accept: application/json" http://localhost:9992/api/trains/bookings/1541683057395
[{"id":"1541683057395","current_train":"TR123","number_places":2}]
# Supprimer une réservation de billets de train par un identifiant (réussie).
$ curl --request DELETE http://localhost:9992/api/trains/bookings/1541683057395 -v
* Connected to localhost (127.0.0.1) port 9992 (#0)
> DELETE /api/trains/bookings/1541685562466 HTTP/1.1
> Host: localhost:9992
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204 No Content
<
* Connection #0 to host localhost left intact
# Comme la méthode DELETE est idempotente possibilité de rappeler plusieurs fois la suppression de billets de train pour un même identifiant (réussie).
$ curl --request DELETE http://localhost:9992/api/trains/bookings/1541683057395 -v
* Connected to localhost (127.0.0.1) port 9992 (#0)
> DELETE /api/trains/bookings/1541685562466 HTTP/1.1
> Host: localhost:9992
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204 No Content
<
* Connection #0 to host localhost left intact
III. Exercice 3 : tests d'intégration de service web REST « Interrogation et réservation de billets de train »▲
III-A. But▲
- Utiliser le framework de test Test-Framework de Jersey.
- Utiliser un style de développement de tests basé sur Given-When-Then.
- Savoir écrire des tests d'intégration.
III-B. Description▲
Ce troisième exercice s'intéresse aux tests d'intégration de service web REST développés avec JAX-RS et Jersey. Le code du service web REST est déjà fourni et correspond à la solution complète de l'exercice 2 sur l'interrogation et la réservation de billets de train. Nous insistons dans cet exercice sur les bonnes pratiques pour développer des tests des services web REST en s'appuyant sur le style Given-When-Then.
Dans la suite de cet exercice, le format de représentation des objets sera du JSON. Pour chaque méthode de test implémentée, vous exécuterez le test unitaire associé.
III-C. Étapes à suivre▲
- Démarrer l'environnement de développement Eclipse.
- Importer le projet Maven jaxrs-tutorial-exercice3 (File -> Import -> Maven -> Existing Maven Projects), choisir le répertoire du projet, puis faire Finish.
Le projet importé contient déjà une implémentation complète du service web REST dédié à l'interrogation et la réservation de billets de train.
- Éditer le fichier de description Maven pom.xml et ajouter les dépendances suivantes afin d'utiliser le framework de test Test-Framework de Jersey.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<dependency>
<groupId>
org.glassfish.jersey.test-framework</groupId>
<artifactId>
jersey-test-framework-core</artifactId>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>
jersey-test-framework-provider-grizzly2</artifactId>
<scope>
test</scope>
</dependency>
- Ouvrir la classe TrainResourceIntegrationTest et compléter le code afin que le framework de test Test-Framework soit supporté.
2.
3.
4.
5.
6.
7.
8.
public
class
TrainResourceIntegrationTest extends
JerseyTest {
@Override
protected
Application configure
(
) {
ResourceConfig resourceConfig =
new
ResourceConfig
(
TrainResource.class
);
resourceConfig.property
(
LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName
(
));
return
resourceConfig;
}
- Ajouter la méthode getTrainsTest qui permet de tester le comportement du service dédié à la récupération de tous les trains /trains.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
@Test
public
void
getTrainsTest
(
) {
// Given
// When
Response response =
target
(
"/trains"
).request
(
MediaType.APPLICATION_JSON_TYPE).get
(
);
// Then
Assert.assertEquals
(
"Http Response should be 200: "
, Status.OK.getStatusCode
(
), response.getStatus
(
));
List<
Train>
readEntities =
response.readEntity
(
new
GenericType<
List<
Train>>(
) {}
);
Assert.assertNotNull
(
readEntities);
Assert.assertEquals
(
3
, readEntities.size
(
));
Assert.assertTrue
(
readEntities.stream
(
).anyMatch
(
current ->
"TR123"
.equals
(
current.getId
(
))));
}
- Compléter la méthode getTrainTest au niveau de la partie When afin de satisfaire les différentes assertions.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
@Test
public
void
getTrainTest
(
) {
// Given
String trainId =
"TR123"
;
// When
// TODO: invoquer le service dédié à la récupération d'un train
// par son identifiant fonctionnel (trainid = "TR123").
// Then
Assert.assertEquals
(
"Http Response should be 200: "
, Status.OK.getStatusCode
(
), response.getStatus
(
));
Assert.assertEquals
(
MediaType.APPLICATION_JSON_TYPE, response.getMediaType
(
));
Train readEntity =
response.readEntity
(
Train.class
);
Assert.assertNotNull
(
readEntity);
Assert.assertEquals
(
"Poitiers"
, readEntity.getDeparture
(
));
}
- Compléter la méthode searchTrainsByCriteriaTest au niveau de la partie Then afin d'ajouter des assertions qui satisfont les contraintes suivantes : le code de statut doit être 200, la réponse doit contenir trois paramètres d'en-tête qui correspondent aux paramètres de la requête initiale (departure, arrival et departure_time) et le contenu doit être une liste de trains d'une taille de deux éléments.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
@Test
public
void
searchTrainsByCriteriaTest
(
) {
// Given
String departure =
"Poitiers"
;
String arrival =
"Paris"
;
String departureTime =
"1710"
;
// When
Response response =
target
(
"/trains"
).path
(
"search"
).queryParam
(
"departure"
, departure)
.queryParam
(
"arrival"
, arrival).queryParam
(
"departure_time"
, departureTime)
.request
(
MediaType.APPLICATION_JSON_TYPE).get
(
);
// Then
// TODO: assertions à respecter ?
// * le code de statut doit être `200` ;
// * la réponse doit contenir trois paramètres d'en-tête qui correspondent
// aux paramètres de la requête initiale (`departure`, `arrival` et `departure_time`) ;
// * le contenu doit être une liste de trains d'une taille de deux éléments.
}
Nous allons maintenant implémenter les tests d'intégration concernant la ressource de réservation de billets de train.
- Ouvrir la classe TrainBookingResourceIntegrationTest et compléter le code afin que le framework de test Test-Framework soit supporté.
2.
3.
4.
5.
6.
public
class
TrainBookingResourceIntegrationTest extends
??? {
@Override
protected
Application configure
(
) {
// TODO: prendre en compte `TrainResource` et `TrainBookingResource`.
}
- Ajouter une méthode createTrainBookingTest qui permet de tester le comportement du service dédié à la création des réservations de billets de train.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@Test
public
void
createTrainBookingTest
(
) {
// Given
TrainBooking trainBooking =
new
TrainBooking
(
);
trainBooking.setNumberPlaces
(
3
);
trainBooking.setTrainId
(
"TR123"
);
// When
// TODO: invoquer le service web pour la création des réservations de billets de train.
// Then
Assert.assertEquals
(
"Http Response should be 200: "
, Status.OK.getStatusCode
(
), response.getStatus
(
));
}
- Ajouter une méthode createTrainBookingWithBadTrainIdTest qui permet de tester que si un identifiant de train n'existe pas, une erreur de type 404 est retournée (NOT_FOUND).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@Test
public
void
createTrainBookingWithBadTrainIdTest
(
) {
// Given
TrainBooking trainBooking =
new
TrainBooking
(
);
trainBooking.setNumberPlaces
(
3
);
trainBooking.setTrainId
(
"BADTR123"
);
// When
// TODO: invoquer le service web pour la création des réservations de billets de train.
// Then
Assert.assertEquals
(
"Http Response should be 404: "
, Status.NOT_FOUND.getStatusCode
(
), response.getStatus
(
));
}
- Ajouter une méthode getTrainBookingsTest qui permet de tester la récupération de toutes les réservations de train. Pour ce test, nous allons utiliser une fabrique de réservations de billets de train dans la partie Given.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
@Test
public
void
getTrainBookingsTest
(
) {
// Given
TrainBooking currentTrainBooking =
createTrainBooking
(
"TR123"
, 3
);
// When
// TODO: invoquer le service web pour la récupérer de toutes
// les réservations de billets de train.
// Then
// TODO: assertions à respecter ?
// * le code statut doit être `200` ;
// * une seule réservation de billets de train ;
// * l'identifiant du train passé lors de la création
// est le même que celui transmis par la réponse.
}
- Ajouter une méthode getTrainBookingTest qui permet de tester la récupération d'une réservation de billets de train à partir d'un identifiant de réservation.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
@Test
public
void
getTrainBookingTest
(
) {
// Given
TrainBooking currentTrainBooking =
createTrainBooking
(
"TR123"
, 3
);
// When
// TODO: invoquer le service web pour la récupération
// d'une réservation de billets de train à partir de currentTrainBooking.getId().
// Then
Assert.assertEquals
(
"Http Response should be 200: "
, Status.OK.getStatusCode
(
), response.getStatus
(
));
}
- Ajouter une méthode getTrainBookingWithBadTrainBookingIdTest qui permet de tester que si un identifiant de réservation de billets de train n'existe pas, le code statut de la réponse doit être 204 puisque la méthode DELETE est idempotente.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@Test
public
void
getTrainBookingWithBadTrainBookingIdTest
(
) {
// Given
String trainBookingId =
"FAKETRAINBOOKINGID"
;
// When
// TODO: invoquer le service web pour la création des réservations de billets de train.
// Then
// TODO: assertion doit vérifier que le code statut est `404`.
}
- Ajouter une méthode removeTrainBookingTest qui permet de tester la suppression d'une réservation d'un billet de train. Le code statut de la réponse doit être 204.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@Test
public
void
removeTrainBookingTest
(
) {
// Given
TrainBooking currentTrainBooking =
createTrainBooking
(
"TR123"
, 3
);
// When
// TODO: invoquer le service web pour la suppression d'une réservation de billets de train à partir de currentTrainBooking.getId()
// Then
// TODO: assertion doit vérifier que le code statut est `204`.
}
- Ajouter une méthode removeTrainBookingWithBadTrainBookingIdTest qui permet de tester que si un identifiant de réservation de billets de train n'existe pas, une erreur de type 404 est retournée (NOT_FOUND).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@Test
public
void
removeTrainBookingWithBadTrainBookingIdTest
(
) {
// Given
String trainBookingId =
"FAKETRAINBOOKINGID"
;
// When
Response response =
target
(
"/trains/bookings"
).path
(
trainBookingId).request
(
MediaType.APPLICATION_JSON_TYPE).delete
(
);
// Then
Assert.assertEquals
(
"Http Response should be 404: "
, Status.NOT_FOUND.getStatusCode
(
), response.getStatus
(
));
}
IV. Exercice 4 : client de service web REST « Interrogation et réservation de billets de train »▲
IV-A. But▲
- Utiliser l'API cliente de JAX-RS et son implémentation via Jersey.
- Savoir manipuler le type de contenu.
IV-B. Description▲
Ce quatrième exercice s'intéresse à la mise en place d'un client pour l'accès au service web REST développé dans les exercices 2 et 3. Une interface graphique développée en Java/Swing permet d'invoquer les services web pour les ressources de train et de réservation de billets de train.
Le projet de l'exercice 3 fournira l'implémentation du service web REST que le client que nous allons développer va invoquer. Exécuter la classe TrainBookingLauncher de l'exercice 3 pour rendre disponible le service web REST.
IV-C. Étapes à suivre▲
- Démarrer l'environnement de développement Eclipse.
- Importer le projet Maven jaxrs-tutorial-exercice4 (File -> Import -> Maven -> Existing Maven Projects), choisir le répertoire du projet, puis faire Finish.
Le projet importé contient déjà une implémentation complète de l'interface graphique développée en Java/Swing. Aucune compétence en Java/Swing n'est demandée puisque l'objectif de cet exercice est de manipuler exclusivement l'API cliente JAX-RS. Si vous souhaitez proposer des améliorations à l'interface graphique, les *pull requests* sont les bienvenues.
- Éditer le fichier de description Maven pom.xml et ajouter la dépendance suivante afin d'utiliser l'API cliente JAX-RS et son implémentation Jersey.
2.
3.
4.
<dependency>
<groupId>
org.glassfish.jersey.core</groupId>
<artifactId>
jersey-client</artifactId>
</dependency>
- Ouvrir la classe TrainBookingsClientMain et compléter le code de la méthode initializeService afin d'initialiser l'attribut WebTarget target. Pour rappel, l'URL d'accès au service web REST est http://localhost:9993/api/trains.
2.
3.
4.
private
void
initializeService
(
) {
Client client =
ClientBuilder.newClient
(
);
target =
client.target
(
"http://localhost:9993/api/trains"
);
}
- Compléter la méthode callGetTrains permettant de récupérer l'ensemble des trains et de les afficher dans le composant graphique JTextArea.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
private
void
callGetTrains
(
) {
// TODO: invoquer le service web REST pour récupérer l'ensemble des trains
// disponibles. Le résultat doit être transmis dans un objet de type
// List<Train>.
trainsConsole.setText
(
""
);
for
(
Train current : result) {
trainsConsole.append
(
current.getId
(
) +
" - "
+
current.getDeparture
(
) +
" - "
+
current.getArrival
(
) +
" - "
+
current.getDepartureTime
(
) +
"
\n
"
);
}
}
La classe Train n'existe pas dans le projet. Comme nous n'utilisons pas une démarche Top/Down, toutes les classes utilisées pour transmettre des données via les requêtes et les réponses ne sont pas générées automatiquement. La solution la plus rapide est de « copier/coller » la classe Train de l'exercice 3.
- Copier depuis l'exercice 3, la classe Train en vous assurant si possible de modifier le nom du package par fr.mickaelbaron.jaxrstutorialexercice4.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
package
fr.mickaelbaron.jaxrstutorialexercice5;
@XmlRootElement
(
name =
"train"
)
public
class
Train {
private
String id;
private
String departure;
private
String arrival;
@JsonProperty
(
"departure_time"
)
private
int
departureTime; // Format : 1230 = 12h30
...
- Compléter la méthode createTrainBooking permettant de créer des réservations de billets de train. Les informations pour la création comme l'identifiant du train et le nombre de places sont renseignées en paramètre de la méthode (valeurs extraites depuis les champs de texte de l'interface graphique).
2.
3.
4.
5.
6.
7.
8.
9.
10.
private
void
createTrainBooking
(
String numTrain, int
numberPlaces) {
// TODO: créer un objet de type TrainBooking.
// TODO: invoquer le service web REST pour créer une réservation de train
// à partir de l'objet TrainBooking.
if
(
Status.OK.getStatusCode
(
) ==
post.getStatus
(
)) {
System.out.println
(
post.readEntity
(
TrainBooking.class
).getId
(
));
}
}
- Compléter la méthode callGetTrainBookings permettant de récupérer l'ensemble des réservations de billets de train et de les afficher dans le composant graphique JTextArea.
2.
3.
4.
5.
6.
7.
8.
9.
private
void
callGetTrainBookings
(
) {
// TODO: invoquer le service web REST pour récupérer l'ensemble des réservations
// de billets de train.
// Le résultat doit être transmis dans un objet de type List<TrainBooking>
for
(
TrainBooking current : result) {
trainBookingsConsole.append
(
current.getId
(
) +
" - "
+
current.getTrainId
(
) +
" - "
+
current.getNumberPlaces
(
));
}
}
Même problème pour la classe TrainBooking qui n'existe pas. Copier depuis l'exercice 3, la classe TrainBooking en vous assurant si possible de modifier le nom du package par fr.mickaelbaron.jaxrstutorialexercice4.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
package
fr.mickaelbaron.jaxrstutorialexercice5;
@XmlRootElement
(
name =
"trainbooking"
)
public
class
TrainBooking {
private
String id;
@JsonProperty
(
"current_train"
)
private
String trainId;
@JsonProperty
(
"number_places"
)
private
int
numberPlaces;
...
Tester votre programme client en invoquant les trois fonctionnalités développées précédemment.
V. Exercice 5 : déployer le service web REST « Interrogation et réservation de billets de train »▲
V-A. But▲
- Déployer comme une application Java classique (exécuter depuis un fichier jar).
- Packager un service web REST dans une archive war.
- Déployer un service web REST sur le serveur d'applications Java Tomcat.
- Gérer les problèmes de dépendances.
V-B. Description▲
Ce cinquième exercice s'intéresse au déploiement du service web REST développé dans les précédents exercices. Nous montrerons le déploiement comme une application Java classique (exécuter depuis un jar) et un déploiement sur le serveur d'application Tomcat (déployer un fichier war). Nous ferons abstraction de l'environnement de développement Eclipse afin d'être le plus proche de l'environnement de production.
Le projet contient tout le code du service web REST.
V-C. Étapes à suivre pour effectuer un déploiement comme une application Java classique▲
- Saisir la ligne de commande suivante depuis la racine du projet pour compiler et construire le fichier jar du projet.
mvn clean package
- Saisir la ligne de commande suivante pour démarrer le projet.
2.
3.
4.
5.
6.
7.
8.
9.
$ java -cp "target/jaxrstutorialexercice5.jar" fr.mickaelbaron.jaxrstutorialexercice5.TrainBookingLauncher
Exception in thread "main" java.lang.NoClassDefFoundError: javax/ws/rs/core/UriBuilder
at fr.mickaelbaron.jaxrstutorialexercice5.TrainBookingLauncher.getBaseURI(TrainBookingLauncher.java:21)
at fr.mickaelbaron.jaxrstutorialexercice5.TrainBookingLauncher.<clinit>(TrainBookingLauncher.java:18)
Caused by: java.lang.ClassNotFoundException: javax.ws.rs.core.UriBuilder
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 2 more
Vous remarquerez que le projet ne démarre pas du fait de l'absence de certaines dépendances. Il est donc obligatoire de fournir les dépendances nécessaires lors de l'exécution (dans le classpath).
- Modifier le fichier pom.xml afin d'ajouter le plugin maven-dependency-plugin qui permettra de lister toutes les bibliothèques nécessaires.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-dependency-plugin</artifactId>
<version>
${maven.dependency.version}</version>
<executions>
<execution>
<id>
copy-dependencies</id>
<phase>
package</phase>
<goals>
<goal>
copy-dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
- Saisir les lignes de commande suivantes pour compiler, construire et démarrer le projet.
2.
3.
4.
5.
6.
7.
8.
9.
$ mvn clean package
...
$ java -cp "target/jaxrstutorialexercice5.jar:target/dependency/*" fr.mickaelbaron.jaxrstutorialexercice5.TrainBookingLauncher
nov. 10, 2018 4:57:35 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:9993]
nov. 10, 2018 4:57:35 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:9993/api/
Hit enter to stop it...
V-D. Étapes à suivre pour effectuer un déploiement sur le serveur d'applications Tomcat▲
Pour un déploiement de service web REST avec Jersey vers un serveur d'applications il est nécessaire de 1) fournir un fichier web.xml où il est précisé un objet ResourceConfig à prendre en compte ; 2) construire le fichier war ; 3) fournir au serveur d'application le fichier war.
- Créer une classe TrainBookingApplication de type ResourceConfig.
2.
3.
4.
5.
6.
7.
8.
9.
package
fr.mickaelbaron.jaxrstutorialexercice5;
public
class
TrainBookingApplication extends
ResourceConfig {
public
TrainBookingApplication
(
) {
this
.registerClasses
(
TrainBookingResource.class
, TrainResource.class
);
this
.property
(
LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName
(
));
}
}
- Éditer le fichier src/main/configuration/web.xml et ajouter la déclaration de la classe TrainBookingApplication.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns
=
"http://xmlns.jcp.org/xml/ns/javaee"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version
=
"3.1"
>
<display-name>
HelloWorldRestWebService</display-name>
<servlet>
<servlet-name>
jersey-servlet</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>
javax.ws.rs.Application</param-name>
<param-value>
fr.mickaelbaron.jaxrstutorialexercice5.TrainBookingApplication</param-value>
</init-param>
<load-on-startup>
1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>
jersey-servlet</servlet-name>
<url-pattern>
/api/*</url-pattern>
</servlet-mapping>
</web-app>
- Éditer le fichier pom.xml et à la ligne 102, préciser le chemin et le nom du fichier web.xml qui sera utilisé comme descripteur pour l'application web Java.
2.
3.
4.
<properties>
<project.packaging>
war</project.packaging>
<maven.war.webxml>
TODO</maven.war.webxml>
</properties>
- Saisir la ligne de commande suivante pour compiler et construire le projet vers un fichier war.
mvn clean package -P war
L'option -P war permet d'utiliser le profil Maven appelé war. Depuis le fichier pom.xml examiner la balise <profiles>. Cette astuce permet de générer un fichier jar ou un fichier war depuis un même fichier pom.xml.
- Saisir la ligne de commande suivante pour télécharger une image Docker de Tomcat.
docker pull tomcat:9-jre11-slim
- Enfin, saisir la ligne de commande suivante pour créer un conteneur Docker qui permettra de démarrer une instance de Tomcat. Le fichier jaxrstutorialexercice5.war contient tous les codes et dépendances de ce projet.
2.
3.
4.
5.
6.
7.
docker run --rm --name helloworldrestservice-tomcat -v $(pwd)/target/jaxrstutorialexercice5.war:/usr/local/tomcat/webapps/jaxrstutorialexercice5.war -it -p 8080:8080 tomcat:9-jre11-slim
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /docker-java-home
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
...
- Tester le service web REST déployé avec cURL.
2.
3.
4.
5.
$ curl --header "Accept: application/xml" http://localhost:8080/jaxrstutorialexercice5/api/trains
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><trains><train><arrival>Paris</arrival><departure>Poitiers</departure><departureTime>1250</departureTime><id>TR123</id></train><train><arrival>Paris</arrival><departure>Poitiers</departure><departureTime>1420</departureTime><id>AX127</id></train><train><arrival>Paris</arrival><departure>Poitiers</departure><departureTime>1710</departureTime><id>PT911</id></train></trains>
$ curl --header "Accept: application/json" http://localhost:8080/jaxrstutorialexercice5/api/trains
[{"id":"TR123","departure":"Poitiers","arrival":"Paris","departure_time":1250},{"id":"AX127","departure":"Poitiers","arrival":"Paris","departure_time":1420},{"id":"PT911","departure":"Poitiers","arrival":"Paris","departure_time":1710}]
VI. Conclusion et remerciements▲
Nous avons vu au travers de quatre exercices comment utiliser l'API JAX-RS. Nous avons notamment insisté sur le développement de services web et de clients utilisés pour appeler les services web de type REST.
Pour aller plus loin, vous pouvez consulter les ressources suivantes :
- Support de cours SOASupport de cours SOA ;
- Support de cours WSDLSupport de cours WSDL ;
- Support de cours SOAPSupport de cours SOAP ;
- Support de cours JAX-WSSupport de cours JAX-WS ;
- Support de cours RESTSupport de cours REST ;
- Support de cours JAX-RSSupport de cours JAX-RS
- Exercices sur le test fonctionnel de services web avec SOAP-UIExercices sur le test fonctionnel de services web avec SOAP-UI ;
- Exercices sur le développement de services web étendus avec JAX-WS et NetbeansExercices sur le développement de services web étendus avec JAX-WS et Netbeans ;
- Exercices sur le développement de services web étendus avec JAX-WS, Maven et EclipseExercices sur le développement de services web étendus avec JAX-WS, Maven et Eclipse ;
- Exercices sur le développement de services web étendus avec JAX-RS et NetbeansExercices sur le développement de services web étendus avec JAX-RS et Netbeans.
Je tiens à remercier jacques_jean pour sa relecture orthographique attentive de cet article.