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

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 9 commentaires Donner une note à l'article (4).

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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

  • Démarrer l'environnement de développement NetBeans.
  • Créer un nouveau projet File -> New Project... puis Java Web et choisir Web Application, faire Next.
  • Dans le nom du projet choisir le nom HelloENSMAWebServiceRestExercice1.
  • Comme type de serveur d'application, choisir GlassFish 3 et comme version Java EE choisir la version 7 puis faire Next.
  • Ne rien choisir dans les options de Frameworks, puis faire Finish. L'initialisation d'un projet Web est en cours.
  • Créer un service Web REST à partir de l'assistant de création Web Services -> RESTful Web Services from Patterns puis faire Next.
  • Choisir par la suite, l'option Simple Root Resource puis faire Next. Dans le champ Resource Package définir la valeur soa.jaxrslabs.helloensmawebservicerestexercice1, dans le champ Path saisir la valeur helloensma, dans le champ Class Name saisir la valeur HelloEnsma, puis faire Finish.
  • Par la suite, il est demandé la manière dont les ressources sont gérées par l'application, choisir la troisième proposition (Create default REST servlet adaptor in web.xml). Notez que pour accéder à toutes les ressources il faudra préfixer les chemins par /resources.
  • Depuis la nouvelle classe créée HelloEnsma, supprimer la méthode correspondant à la création d'une ressource HelloEnsma (méthode putXml).
  • Dans le corps de la méthode String getXml() utilisée pour récupérer la ressource Bonjour ENSMA, recopier le contenu suivant
 
Sélectionnez
1.
2.
3.
4.
5.
@GET
@Produces("application/xml")
public String getXml() {
    return "<bonjour>Bonjour ENSMA</bonjour>";
}
  • Faire un Clean and Build à partir du projet HelloENSMAWebServiceRestExercice1 et s'assurer que le projet se construit correctement.
  • Faire un Deploy à partir du projet. Le serveur Glassfish doit démarrer. Sur la console de Glassfish, un Web Service doit avoir été découvert, un message similaire doit être présent :
Image non disponible
  • 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 :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@GET
@Path("{id}")
@Produces("application/xml")
public Response getXmlWithParams(@PathParam("id") String id, @DefaultValue("all") @HeaderParam("name") String name) {
    System.out.println(id);
    System.out.println(name);
    return Response
      .status(Status.OK)
      .entity("<bonjour>Bonjour ENSMA de la part de " + name + "</bonjour>")
      .build();
}
  • Saisir l'URL suivante depuis votre navigateur Web pour tester la récupération de la ressource Bonjour ENSMA : http://localhost:8080/HelloENSMAWebServiceRestExercice1/resources/helloensma/27022011. Le message au format XML est retourné au navigateur.
  • Pour envoyer des paramètres au niveau de l'en-tête de la requête, nous utiliserons l'outil CURL qui est un outil en ligne de commande pour construire des requêtes HTTP. Télécharger et installer l'outil à partir de l'URL suivante : http://curl.haxx.se/.
  • Depuis une invite de commande saisir la commande suivante :
Image non disponible
  • À partir d'un navigateur Web, afficher le contrat de description WADL via l'URL suivante : http://localhost:8080/HelloENSMAWebServiceRestExercice1/resources/application.wadl.
  • Utiliser l'outil de test de service Rest fourni par Netbeans (menu contextuel du projet et choisir l'élément Test RESTful Web Services).
  • Envoyer une requête HTTP de type GET pour récupérer une ressource Bonjour ENSMA. Noter qu'il n'est pas possible avec cet outil de modifier l'en-tête d'une requête HTTP.
  • Télécharger et installer l'outil SOAP-UI à partir de l'URL suivante : http://www.soapui.org/. Ouvrir SOAP-UI et créer un projet à partir de ce contrat de description.
  • Envoyer une requête HTTP de type GET pour récupérer une ressource Bonjour ENSMA. Modifier cette requête de façon à transmettre également les paramètres via le chemin et via l'en-tête de la requête HTTP.

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

  • Créer un nouveau projet File -> New Project... puis Java Web et choisir Web Application, faire Next.
  • Dans le nom du projet choisir le nom BookTrainWebServiceRestExercice2.
  • Comme type de serveur d'application, choisir GlassFish 3 et comme version Java EE choisir la version 6 puis faire Next.
  • Ne rien choisir dans les options de Frameworks, puis faire Finish. L'initialisation d'un projet Web est en cours.
  • Créer la classe Train (dans le package soa.jaxrslabs.booktrainwebservicerestexercice2) qui modélise le concept de Train et qui contient un attribut String numTrain (identifiant fonctionnel d'un train), un attribut String villeDepart (la ville de départ du train), un attribut String villeArrivee (la ville d'arrivée du train), un attribut int heureDepart (heure de départ depuis la ville de départ). Ajouter des modificateurs et des accesseurs sur tous les attributs. Voir le code suivant
 
Sélectionnez
1.
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.
@XmlRootElement(name = "train")
public class Train {

    private String numTrain;
    private String villeDepart;
    private String villeArrivee;
    private int heureDepart; // Format : 1230 = 12h30

    public Train() {        
    }

    public Train(String numTrain, String villeDepart, String villeArrivee, int heureDepart) {
        this.numTrain = numTrain;
        this.villeDepart = villeDepart;
        this.villeArrivee = villeArrivee;
        this.heureDepart = heureDepart;
    }

    public int getHeureDepart() {
        return heureDepart;
    }

    public void setHeureDepart(int heureDepart) {
        this.heureDepart = heureDepart;
    }

	... // A compléter...
}
  • Créer la classe BookTrainBD qui sert à persister toutes les informations concernant le service Web de cet exercice.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
public class BookTrainBD {

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

    static {
        trains.add(new Train("TR123", "Poitiers", "Paris", 1250));
        trains.add(new Train("TR127", "Poitiers", "Paris", 1420));
        trains.add(new Train("TR129", "Poitiers", "Paris", 1710));
    }

    public static List<Train> getTrains() {
        return trains;
    }
}
  • 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.
 
Sélectionnez
1.
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.
@Path("/trains")
@Produces("application/xml")
public class TrainResource {

    public TrainResource() {        
    }

    @GET // Méthode HTTP utilisée pour déclencher cette méthode
    public Response getTrains() {
        System.out.println("getTrains");

        return Response
          .status(Status.OK)
          .entity(BookTrainBD.getTrains())
          .build();
    }

    @GET // Méthode HTTP utilisée pour déclencher cette méthode
    @Path("numTrain-{id}") // Chemin suivant ce template numTrain-TR123  TR123 est une chaine variable
    public Response getTrain(@PathParam("id") String numTrain) {
        System.out.println("getTrain");

        for (Train current : BookTrainBD.getTrains()) {
            if (numTrain.equals(current.getNumTrain())) {
              return Response
                .status(Status.OK)
                .entity(current)
                .build();
            }
        }
        return Response
          .status(Status.NO_CONTENT)
          .build();
    }

    @GET // Méthode HTTP utilisée pour déclencher cette méthode
    @Path("/search") // Chemin suivant /trains/search pour invoquer cette méthode
    public List<Train> searchTrainsByCriteria(@QueryParam("departure") String departure, @QueryParam("arrival")
        String arrival, @QueryParam("arrivalhour") String arrivalHour) {
        System.out.println("searchTrainsByCriteria");

        return Response
          .status(Status.OK)
          .entity(BookTrainBD.getTrains().subList(0, 2))
          .build();
    }
}

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

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.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
@XmlRootElement(name = "booktrain")
public class BookTrain {

    private String bookNumber;

    private Train currentTrain;

    private int numberPlaces;

    public String getBookNumber() {
        return bookNumber;
    }

    public void setBookNumber(String bookNumber) {
        this.bookNumber = bookNumber;
    }

    public Train getCurrentTrain() {
        return currentTrain;
    }

	...
}

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

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
public class BookTrainBD {

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

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

    static {
        trains.add(new Train("TR123", "Poitiers", "Paris", 1250));
        trains.add(new Train("TR127", "Poitiers", "Paris", 1420));
        trains.add(new Train("TR129", "Poitiers", "Paris", 1710));
    }

    public static List<Train> getTrains() {
        return trains;
    }

    public static List<BookTrain> getBookTrains() {
        return bookTrains;
    }
}

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.

 
Sélectionnez
1.
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.
60.
61.
62.
63.
64.
65.
66.
67.
68.
public class BookTrainResource {

    @POST // Méthode HTTP utilisée pour déclencher cette méthode
    public Response createBookTrain(String numTrain, int numberPlaces) {
        Train currentTrain = null;
        for (Train current : BookTrainBD.getTrains()) {
            if (current.getNumTrain().equals(numTrain)) {
                currentTrain = current;
            }
        }        
        if (currentTrain == null) {
          return Response
            .status(Status.NO_CONTENT)
            .build();
        }

        BookTrain newBookTrain = new BookTrain();
        newBookTrain.setNumberPlaces(numberPlaces);
        newBookTrain.setCurrentTrain(currentTrain);
        newBookTrain.setNumBook(Long.toString(System.currentTimeMillis()));
        BookTrainBD.getBookTrains().add(newBookTrain);

        return Response
          .status(Status.OK)
          .entity(newBookTrain.getNumBook())
          .build();
    }

    @GET // Méthode HTTP utilisée pour déclencher cette méthode
    public Response getBookTrains() {
        System.out.println("getBookTrains");

        return Response
          .status(Status.OK)
          .entity(BookTrainBD.getBookTrains())
          .build();
    }

    @GET // Méthode HTTP utilisée pour déclencher cette méthode
    @Path("{id}") // Chemin de façon à intégrer un template parameter (id)
    public Response getBookTrain(@PathParam("id") String bookNumber) {
        List<BookTrain> bookTrains = BookTrainBD.getBookTrains();

        for (BookTrain current : bookTrains) {
            if (current.getNumBook().equals(bookNumber)) {
              return Response
                .status(Status.OK)
                .entity(current)
                .build();
            }
        }
        return Response
          .status(Status.NO_CONTENT)
          .build();
    }

    @DELETE // Méthode HTTP utilisée pour déclencher cette méthode
    @Path("{id}") // Chemin de façon à intégrer un template parameter (id)
    public Response removeBookTrain(@PathParam("id") String bookNumber) {
        BookTrain currentBookTrain = null;
        for (BookTrain current : BookTrainBD.getBookTrains()) {
            if (current.getNumBook().equals(bookNumber)) {
                currentBookTrain = current;
            }
        }
        return Response.status(Status.ACCEPTED).build();
    }
}

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.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
public class TrainResource {

    public TrainResource() {        
    }

    @Path("/booktrains") // Chemin suivant /trains/booktrains pour invoquer cette méthode
    public BookTrainResource getBookTrainResource() {
        return new BookTrainResource();
    }
}

À 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.

Image non disponible

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())

 
Sélectionnez
1.
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.
public class BookTrainClientMain extends JFrame {

    public BookTrainClientMain() {
        super("Client Réservation Train");

        this.initializeService();
        this.setLayout(new GridLayout(3,1,10,10));
        JPanel panelTrains = new JPanel();
        panelTrains.setLayout(new GridLayout(1,1));

        JButton getTrains = new JButton("GetTrains");
        panelTrains.add(getTrains);
        getTrains.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                callGetTrains();
            }
        });

        JPanel panelBookTrains = new JPanel();
        panelBookTrains.setLayout(new GridLayout(1,1));

        JButton getBookTrains = new JButton("GetBookTrains");
        panelBookTrains.add(getBookTrains);
        getBookTrains.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                callGetBookTrains();
            }
        });

        JPanel createBookTrainFormPanel = new JPanel();
        createBookTrainFormPanel.setLayout(new GridLayout(1,3));
        final JTextField numTrain = new JTextField();
        final JTextField numberPlaces = new JTextField();
        JButton createBookTrains = new JButton("CreateBookTrain");
        createBookTrains.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                createBookTrains(numTrain.getText(), numberPlaces.getText());
            }
        });
        createBookTrainFormPanel.add(createBookTrains);
        createBookTrainFormPanel.add(numTrain);
        createBookTrainFormPanel.add(numberPlaces);


        this.add(panelTrains);
        this.add(panelBookTrains);
        this.add(createBookTrainFormPanel);

        this.pack();
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new BookTrainClientMain();
    }
}

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.

 
Sélectionnez
1.
2.
3.
4.
5.
private void initializeService() {
    ClientConfig config = new DefaultClientConfig();
    Client client = Client.create(config);
    service = client.resource(UriBuilder.fromUri("http://localhost:8080/BookTrainWebServiceRestExercice2/resources/").build());
}

Ajouter également un attribut service de type WebResource.

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

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
private void callGetTrains() {
	List<Train> result = service
		.path("trains")
          .accept(MediaType.APPLICATION_XML)
          .get(new GenericType<List<Train>>() {});
	for(Train current : result) {
		System.out.println(current.getNumTrain() + " - " + current.getVilleDepart() + " - " +
			current.getVilleArrivee() + " - " + current.getHeureDepart());
    }
}

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).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
private void createBookTrains(String numTrain, String numberPlaces) {
    String numBook = service.path("trains")
        .path("booktrains")
        .queryParam("numTrain", numTrain)
        .queryParam("numberPlaces", numberPlaces).request()
        .post(null, String.class);

    System.out.println(numBook);
}

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.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
private void callGetBookTrains() {
    List<BookTrain> result = service
        .path("trains")
        .path("booktrains")
        .accept(MediaType.APPLICATION_XML)
        .get(new GenericType<List<BookTrain>>() {});

	for(BookTrain current : result) {
        System.out.println(current.getNumBook() + " - " + current.getCurrentTrain().getNumTrain() + " - "  +
        current.getNumberPlaces());
    }
}

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

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

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.