Tutoriel sur le développement de services Web REST avec JAX-RS et Netbeans : Leçon 3

Mickael BARON

Le but de cette troisième leçon est d'apprendre à manipuler l'API JAX-RS pour le développement de services Web REST à partir de la plateforme de développement Java. La leçon insiste sur les développements serveur et client d'un service Web REST.

Buts pédagogiques : transformation d'une classe Java en service Web REST, manipulation des annotations JAX-RS, génération de fichier WADL, utilisation de SOAP-UI pour invoquer un service Web REST à partir de WADL, utilisation de l'implémentation JERSEY, utilisation de l'API cliente de JERSEY.

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel sur le développement de services Web REST avec JAX-RS et Netbeans : Leçon 3

- Auteur : Mickael BARON

- Parution : 7 septembre 2011

- Mise à jour : 15 janvier 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Mickael Baron. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Exercice 1 - Développer un Service Web REST : Bonjour ENSMA

- II-A. But

- II-B. Description

- II-C. Étapes à suivre

- II. Exercice 2 - Développer un Service Web REST : Interrogation et réservation de train

- III-A. But

- III-B. Description

- III-C. Étapes à suivre

- III. Exercice 3 - Développer un client de Service Web REST : Interrogation et réservation de train

- IV-A. But

- IV-B. Description

- IV-C. Étapes à suivre

- IV. Remerciements

I - Exercice 1 - Développer un Service Web REST : Bonjour ENSMA

II-A - But

- Développer un service web REST à partir d'une classe Java.

- Déployer sous Glashfish, tester le service web avec CURL.

- Tester le service web avec SOAP-UI.

- Tester le service web avec les outils de Netbeans.

II-B - Description

Le service Web de type REST de ce premier exercice consiste à fournir un accès à la ressource Bonjour ENSMA qui permet de donner des messages différents selon la date du jour. Les services proposés n'autorisent que la lecture à la ressource (GET). Ces services de récupération peuvent posséder des paramètres. Enfin, le format de récupération de la ressource est du XML.

II-C - Étapes à suivre

1. @GET

2. @Produces("application/xml")

3. public String getXml() {

4. return "<bonjour>Bonjour ENSMA</bonjour>";

5. }

- Ouvrir une fenêtre d'un navigateur Web et tester la récupération de la ressource Bonjour ENSMA (requête GET via l'URL http://localhost:8080/HelloENSMAWebServiceRestExercice1/resources/helloensma).

Ce premier service REST n'est pas parfait puisque le type de retour est une simple chaîne de caractères. Comment pourrions-nous retourner à la fois une information, un code de statut ou des informations dans l'en-tête de la réponse ? Pour cela, nous allons utiliser un objet Response pour le retour des prochains services REST.

- Ajouter une nouvelle méthode Java getXmlWithParams dans la classe HelloEnsma qui permet de récupérer la ressource Bonjour ENSMA via des paramètres de requêtes. Ces paramètres seront donnés via le chemin et via l'en-tête de la requête en utilisant respectivement les annotations @PathParam et @HeaderParam. Recopier le code suivant :

1. @GET

2. @Path("{id}")

3. @Produces("application/xml")

4. public Response getXmlWithParams(@PathParam("id") String id, @DefaultValue("all") @HeaderParam("name") String name) {

5. System.out.println(id);

6. System.out.println(name);

7. return Response

8. .status(Status.OK)

9. .entity("<bonjour>Bonjour ENSMA de la part de " + name + "</bonjour>")

10. .build();

11. }

II - Exercice 2 - Développer un Service Web REST : Interrogation et réservation de train

III-A - But

- Développer un service web REST à partir d'une classe Java.

- Utilisation d'un Sub-Resource Locator.

- Mise en place d'un CRUD complet.

- Déployer sous GlassFish et tester le service web avec CURL.

III-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 trains. Les ressources manipulées par les services sont donc un train et une réservation. 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), de créer, de modifier, de supprimer et de lister une réservation pour un client donné. Nous insisterons sur l'accessibilité des services et non sur le code métier.

III-C - Étapes à suivre

1. @XmlRootElement(name = "train")

2. public class Train {

4. private String numTrain;

5. private String villeDepart;

6. private String villeArrivee;

7. private int heureDepart; // Format : 1230 = 12h30

9. public Train() {

10. }

12. public Train(String numTrain, String villeDepart, String villeArrivee, int heureDepart) {

13. this.numTrain = numTrain;

14. this.villeDepart = villeDepart;

15. this.villeArrivee = villeArrivee;

16. this.heureDepart = heureDepart;

17. }

19. public int getHeureDepart() {

20. return heureDepart;

21. }

23. public void setHeureDepart(int heureDepart) {

24. this.heureDepart = heureDepart;

25. }

27. ... // A compléter...

28. }

- Créer la classe BookTrainBD qui sert à persister toutes les informations concernant le service Web de cet exercice.

1. public class BookTrainBD {

3. private static List<Train> trains = new ArrayList<Train>();

5. static {

6. trains.add(new Train("TR123", "Poitiers", "Paris", 1250));

7. trains.add(new Train("TR127", "Poitiers", "Paris", 1420));

8. trains.add(new Train("TR129", "Poitiers", "Paris", 1710));

9. }

11. public static List<Train> getTrains() {

12. return trains;

13. }

14. }

- Créer la classe TrainResource (dans le package soa.jaxrslabs.booktrainwebservicerestexercice2) permettant l'accès aux services Web REST de la ressource Train. Définir comme chemin de ressource racine la valeur /trains (utilisation de l'annotation @Path) puis ajouter trois méthodes qui permettent respectivement de retourner la liste des trains, un train en fonction de son identifiant fonctionnel et une recherche de trains par critères passés en paramètre de la requête (ville de départ, ville d'arrivée et heure de départ). Pour cette dernière méthode, le sous-chemin associé est /search. Noter que le format de retour des services est du XML.

1. @Path("/trains")

2. @Produces("application/xml")

3. public class TrainResource {

5. public TrainResource() {

6. }

8. @GET // Méthode HTTP utilisée pour déclencher cette méthode

9. public Response getTrains() {

10. System.out.println("getTrains");

12. return Response

13. .status(Status.OK)

14. .entity(BookTrainBD.getTrains())

15. .build();

16. }

18. @GET // Méthode HTTP utilisée pour déclencher cette méthode

19. @Path("numTrain-{id}") // Chemin suivant ce template numTrain-TR123 où TR123 est une chaine variable

20. public Response getTrain(@PathParam("id") String numTrain) {

21. System.out.println("getTrain");

23. for (Train current : BookTrainBD.getTrains()) {

24. if (numTrain.equals(current.getNumTrain())) {

25. return Response

26. .status(Status.OK)

27. .entity(current)

28. .build();

29. }

30. }

31. return Response

32. .status(Status.NO_CONTENT)

33. .build();

34. }

36. @GET // Méthode HTTP utilisée pour déclencher cette méthode

37. @Path("/search") // Chemin suivant /trains/search pour invoquer cette méthode

38. public List<Train> searchTrainsByCriteria(@QueryParam("departure") String departure, @QueryParam("arrival")

39. String arrival, @QueryParam("arrivalhour") String arrivalHour) {

40. System.out.println("searchTrainsByCriteria");

42. return Response

43. .status(Status.OK)

44. .entity(BookTrainBD.getTrains().subList(0, 2))

45. .build();

46. }

47. }

À partir d'un navigateur Web invoquer les trois services définis ci-dessus.

- http://localhost:8080/BookTrainWebServiceRestExercice2/resources/trains.

- http://localhost:8080/BookTrainWebServiceRestExercice2/resources/trains/numTrain-TR123

- http://localhost:8080/BookTrainWebServiceRestExercice2/resources/trains/search?departure=poitiers&arrival=paris&arrivalhour=1250

Créer la classe BookTrain (toujours dans le package soa.jaxrslabs.booktrainwebservicerestexercice2) qui modélise le concept de réservation de Train et qui contient un attribut String numBook (identifiant fonctionnel de la réservation d'un train), un attribut Train currentTrain (association sur le train de la réservation), un attribut int numberPlaces (nombre de places réservées). Ajouter des modificateurs et des accesseurs sur tous les attributs.

1. @XmlRootElement(name = "booktrain")

2. public class BookTrain {

4. private String bookNumber;

6. private Train currentTrain;

8. private int numberPlaces;

10. public String getBookNumber() {

11. return bookNumber;

12. }

14. public void setBookNumber(String bookNumber) {

15. this.bookNumber = bookNumber;

16. }

18. public Train getCurrentTrain() {

19. return currentTrain;

20. }

22. ...

23. }

Compléter la classe BookTrainBD de façon à persister les informations concernant les services liés à la réservation de train.

1. public class BookTrainBD {

3. private static List<Train> trains = new ArrayList<Train>();

5. private static List<BookTrain> bookTrains = new ArrayList<BookTrain>();

7. static {

8. trains.add(new Train("TR123", "Poitiers", "Paris", 1250));

9. trains.add(new Train("TR127", "Poitiers", "Paris", 1420));

10. trains.add(new Train("TR129", "Poitiers", "Paris", 1710));

11. }

13. public static List<Train> getTrains() {

14. return trains;

15. }

17. public static List<BookTrain> getBookTrains() {

18. return bookTrains;

19. }

20. }

Créer la classe BookTrainResource (dans le package soa.jaxrslabs.booktrainwebservicerestexercice2) permettant l'accès aux services Web REST de la ressource Réservation. Quatre méthodes sont à définir. La première createBookTrain est invoquée pour la création d'une ressource Réservation (méthode POST). La deuxième getBookTrains (méthode GET) est utilisée pour lister l'ensemble des réservations. La troisième getBookTrain (méthode GET) permet de retourner les informations d'une réservation à partir d'un numéro de réservation. Finalement removeBookTrain (méthode DELETE) permet de supprimer une réservation.

1. public class BookTrainResource {

3. @POST // Méthode HTTP utilisée pour déclencher cette méthode

4. public Response createBookTrain(String numTrain, int numberPlaces) {

5. Train currentTrain = null;

6. for (Train current : BookTrainBD.getTrains()) {

7. if (current.getNumTrain().equals(numTrain)) {

8. currentTrain = current;

9. }

10. }

11. if (currentTrain == null) {

12. return Response

13. .status(Status.NO_CONTENT)

14. .build();

15. }

17. BookTrain newBookTrain = new BookTrain();

18. newBookTrain.setNumberPlaces(numberPlaces);

19. newBookTrain.setCurrentTrain(currentTrain);

20. newBookTrain.setNumBook(Long.toString(System.currentTimeMillis()));

21. BookTrainBD.getBookTrains().add(newBookTrain);

23. return Response

24. .status(Status.OK)

25. .entity(newBookTrain.getNumBook())

26. .build();

27. }

29. @GET // Méthode HTTP utilisée pour déclencher cette méthode

30. public Response getBookTrains() {

31. System.out.println("getBookTrains");

33. return Response

34. .status(Status.OK)

35. .entity(BookTrainBD.getBookTrains())

36. .build();

37. }

39. @GET // Méthode HTTP utilisée pour déclencher cette méthode

40. @Path("{id}") // Chemin de façon à intégrer un template parameter (id)

41. public Response getBookTrain(@PathParam("id") String bookNumber) {

42. List<BookTrain> bookTrains = BookTrainBD.getBookTrains();

44. for (BookTrain current : bookTrains) {

45. if (current.getNumBook().equals(bookNumber)) {

46. return Response

47. .status(Status.OK)

48. .entity(current)

49. .build();

50. }

51. }

52. return Response

53. .status(Status.NO_CONTENT)

54. .build();

55. }

57. @DELETE // Méthode HTTP utilisée pour déclencher cette méthode

58. @Path("{id}") // Chemin de façon à intégrer un template parameter (id)

59. public Response removeBookTrain(@PathParam("id") String bookNumber) {

60. BookTrain currentBookTrain = null;

61. for (BookTrain current : BookTrainBD.getBookTrains()) {

62. if (current.getNumBook().equals(bookNumber)) {

63. currentBookTrain = current;

64. }

65. }

66. return Response.status(Status.ACCEPTED).build();

67. }

68. }

L'accès à la ressource Réservation (via la classe BookTrainRessource) est obtenu via l'utilisation d'un sub-resource locator. Compléter la classe TrainResource.

1. public class TrainResource {

3. public TrainResource() {

4. }

6. @Path("/booktrains") // Chemin suivant /trains/booktrains pour invoquer cette méthode

7. public BookTrainResource getBookTrainResource() {

8. return new BookTrainResource();

9. }

10. }

À partir de CURL et SOAP-UI, invoquer chaque service (création d'une réservation, liste les réservations, obtenir les informations d'une réservation et supprimer la réservation créée).

III - Exercice 3 - Développer un client de Service Web REST : Interrogation et réservation de train

IV-A - But

- Développer un client de service web REST à partir de l'API JERSEY.

- Création des requêtes via le patron Builder.

IV-B - Description

Ce troisième exercice se propose de fournir un client pour l'accès au service Web REST défini dans l'exercice 2. Une interface graphique en Java/Swing permet de contrôler les appels aux différents services.

IV-C - Étapes à suivre

Créer un nouveau projet File -> New Project... puis Java et choisir Java Application, faire Next.

Pour le nom du projet choisir le nom BookTrainClientWebServiceRestExercice3.

Pour le nom de la classe principale choisir soa.jaxrslabs.booktrainclientwebservicerestexercice3.BookTrainClientMain (Create Main Class).

Ajouter les bibliothèques Jersey (JAX-RS RI) et la bibliothèque décrivant l'API JAX-RS.

Compléter la classe BookTrainClientMain à partir du code ci-dessous. Noter que les traitements des trois boutons seront à compléter par la suite (callGetTrains(), callGetBookTrains(), createBookTrains())

1. public class BookTrainClientMain extends JFrame {

3. public BookTrainClientMain() {

4. super("Client Réservation Train");

6. this.initializeService();

7. this.setLayout(new GridLayout(3,1,10,10));

8. JPanel panelTrains = new JPanel();

9. panelTrains.setLayout(new GridLayout(1,1));

11. JButton getTrains = new JButton("GetTrains");

12. panelTrains.add(getTrains);

13. getTrains.addActionListener(new ActionListener() {

14. public void actionPerformed(ActionEvent e) {

15. callGetTrains();

16. }

17. });

19. JPanel panelBookTrains = new JPanel();

20. panelBookTrains.setLayout(new GridLayout(1,1));

22. JButton getBookTrains = new JButton("GetBookTrains");

23. panelBookTrains.add(getBookTrains);

24. getBookTrains.addActionListener(new ActionListener() {

25. public void actionPerformed(ActionEvent e) {

26. callGetBookTrains();

27. }

28. });

30. JPanel createBookTrainFormPanel = new JPanel();

31. createBookTrainFormPanel.setLayout(new GridLayout(1,3));

32. final JTextField numTrain = new JTextField();

33. final JTextField numberPlaces = new JTextField();

34. JButton createBookTrains = new JButton("CreateBookTrain");

35. createBookTrains.addActionListener(new ActionListener() {

36. public void actionPerformed(ActionEvent e) {

37. createBookTrains(numTrain.getText(), numberPlaces.getText());

38. }

39. });

40. createBookTrainFormPanel.add(createBookTrains);

41. createBookTrainFormPanel.add(numTrain);

42. createBookTrainFormPanel.add(numberPlaces);

45. this.add(panelTrains);

46. this.add(panelBookTrains);

47. this.add(createBookTrainFormPanel);

49. this.pack();

50. this.setVisible(true);

51. }

53. public static void main(String[] args) {

54. new BookTrainClientMain();

55. }

56. }

Ajouter une méthode initializeService() qui permet d'initialiser l'accès au service Web REST. Adapter l'URL selon l'emplacement déterminé par l'exercice 2.

1. private void initializeService() {

2. ClientConfig config = new DefaultClientConfig();

3. Client client = Client.create(config);

4. service = client.resource(UriBuilder.fromUri("http://localhost:8080/BookTrainWebServiceRestExercice2/resources/").build());

5. }

Ajouter également un attribut service de type WebResource.

Ajouter une méthode callGetTrains() qui permet de lister l'ensemble des trains.

1. private void callGetTrains() {

2. List<Train> result = service

3. .path("trains")

4. .accept(MediaType.APPLICATION_XML)

5. .get(new GenericType<List<Train>>() {});

6. for(Train current : result) {

7. System.out.println(current.getNumTrain() + " - " + current.getVilleDepart() + " - " +

8. current.getVilleArrivee() + " - " + current.getHeureDepart());

9. }

10. }

Recopier dans le projet BookTrainClientWebServiceRestExercice3 la classe Train depuis le projet BookTrainWebServiceRestExercice2 en respectant le package (soa.jaxrslabs.booktrainclientwebservicerestexercice3).

Ajouter une méthode createBookTrains(String numTrain, String numberPlaces) qui permet de créer une réservation d'un train. Compléter le code ci-dessous de façon à prendre en compte les paramètres de requêtes et l'appel à une méthode HTTP POST. Pour rappel, le service de création retourne un numéro de réservation (type String).

1. private void createBookTrains(String numTrain, String numberPlaces) {

2. String numBook = service.path("trains")

3. .path("booktrains")

4. .queryParam("numTrain", numTrain)

5. .queryParam("numberPlaces", numberPlaces).request()

6. .post(null, String.class);

8. System.out.println(numBook);

9. }

Ajouter une méthode callGetBookTrains() qui permet de récupérer l'ensemble des réservations qui ont été créées. Compléter le code ci-dessous.

1. private void callGetBookTrains() {

2. List<BookTrain> result = service

3. .path("trains")

4. .path("booktrains")

5. .accept(MediaType.APPLICATION_XML)

6. .get(new GenericType<List<BookTrain>>() {});

8. for(BookTrain current : result) {

9. System.out.println(current.getNumBook() + " - " + current.getCurrentTrain().getNumTrain() + " - "  +

10. current.getNumberPlaces());

11. }

12. }

Recopier dans le projet BookTrainClientWebServiceRestExercice3 la classe BookTrain depuis le projet BookTrainWebServiceRestExercice2 en respectant le package (soa.jaxrslabs.booktrainclientwebservicerestexercice3).

Exécuter le programme en vous assurant que le service Web REST défini dans l'exercice 2 est déployé et opérationnel.

IV - Remerciements

- https://mbaron.developpez.com/soa/soapuilesson1: Tests fonctionnels de Web Services avec SOAP-UI.

- https://mbaron.developpez.com/soa/jaxwslesson2: Développement de Web Services avec JAX-WS et Netbeans.

Je tiens à remercier Claude Leloup pour sa relecture orthographique attentive de cet article.