Vous êtes sur la page 1sur 88

Les services web

Chapitres traits : Prsentation des services web


Avec la plateforme Java EE, nous avons dcouvert de nombreux services diffrents, qui sont trs faciles dvelopper (codage extrmement simplifi), grce la technique des objets distants. Dans un premier temps, nous avons utiliser les beans sessions qui s'intressent la logique mtier. Ce sont des objets distants facile manipuler. Effectivement, avec une application fentre classique, il suffit d'appeler les mthodes de ces objets comme s'ils taient prsent sur le poste local. Par contre, pour que ce fonctionnement simple puisse s'tablir, vous devez rester dans le rseau local de l'entreprise (le numro de port de ces services va tre bloqu par le pare-feu). Si vous dsirez utiliser ces services depuis Internet, nous avons dcouvert que nous devions passer par une application Web qui elle-mme communique, en local, aux diffrents beans sessions. Cela fonctionne trs bien, mais nous devons passer systmatiquement par un navigateur Web, ce qui limite beaucoup la prsentation et l'ergonomie (il s'agit d' un client lger). Effectivement, nous travaillons uniquement l'aide de page Web.

Le top du top serait de pouvoir faire comme en rseau local, c'est--dire de pouvoir utiliser une application fentre, qui fait appel aux diffrents services, tout en tant sur Internet, et donc sans passer par un navigateur. Il existe une solution pour cela, il s'agit des services web. En ralit, ces services Web communiquent comme une application Web, c'est--dire au travers du protocole HTTP (ce qui permet la communication par Internet).

Au travers de ce protocole HTTP, l'appel des diffrentes mthodes de l'objet distant se fait l'aide d'un document XML, qui est envoy et interprt. Ensuite, le service est rendu par l'envoi galement d'un autre document XML. Ces documents XML respectent un standard (SOAP) propre au service Web. Le gros avantage de cette dmarche, c'est que vous pouvez dvelopper votre service Web avec le langage que vous voulez, de mme que pour l'application cliente. Ainsi par exemple, vous pouvez faire votre service web l'aide de la plate-forme Java EE et dvelopper votre application cliente en .NET. C'est ce que nous appelons : Interoprabilit.

Nous imaginons aisment que toute cette technique est complique. Effectivement elle l'est. Toutefois, grce aux outils de dveloppement comme NetBeans, la fabrication d'un service Web est trs facile mettre en oeuvre. Toute la logique de bas niveau est automatiquement gre. Dans cette tude, nous aborderons ce sujet au travers de cet environnement afin d'viter cette complexit. Il faut dire aussi que Java EE 6 rduit considrablement toute la complexit en s'occupant automatiquement de toute l'ossature de bas niveau. Une autre alternative, malgr qu'elle ne soit pas standard consiste utiliser directement le protocole HTTP sans couche supplmentaire, l'aide cette fois-ci d'un web service dnomm, service web REST. Cette approche est plus simple implmenter. Par ailleurs, les services web, dans ce cas l, sont plutt associs grer des ressources distantes avec toutes les phases classiques, de cration, de rcupration, de modification et de suppression (CRUD). Ces services web REST seront exploits durant le dernier chapitre de cette tude.

Prsentation des services web


Avant d'expliquer ce qu'est un service web (Web Service), nous allons voir pourquoi ils existent. Auparavant, pour mettre en place des applications distribues, les dveloppeurs devaient mettre en place des architectures type Corba dans le cas d'applications htrognes, RMI en environnement Java ou encore DCOM chez Microsoft. Pour faciliter la coordination de diffrents systmes htrognes, les grands diteurs comme SUN, IBM, Oracle... ont dcid d'tablir un standard de communication : les services web. Les services web permettent ainsi, ce que nous avons nomm, l'interoprabilit. Pour rsumer, les services web sont une sorte de logique mtier offerte une application cliente (c'est--dire un consomateur de service) via une interface de service. A la diffrence des objets ou des EJBs, les services web fournissent une interface faiblement couple en se servant de XML. Les standards prcisent que l'interface laquelle nous envoyons un message doit dfinir le format du message de requte et de rponse, ainsi que les mcanismes pour publier et pour dcouvrir les interfaces du service (une base de registres). Nous dcouvrons ici l'interaction d'un service web de faon trs abstraite. Le service peut ventuellement enregistrer son interface dans une base de registres (UDDI) afin qu'un consomateur puisse le trouver. Une fois que le consommateur connat l'interface du service et le format du message, il peut envoyer une requte et recevoir une rponse.

Technologie et protocoles
Les services web ncessitent plusieurs technologies et protocoles pour transporter et transformer les donnes d'un client vers un service de faon standard. Les plus courants sont les suivants : UDDI (Universal Description, Discovery and Integration) : est une base de registres et un mcanisme de dcouverte qui ressemble aux pages jaunes. Il sert stocker et classer des services web. UDDI est la spcification dfinissant la manire de publier et de retrouver des services web. C'est un annuaire qui offre des mcanismes d'enregistrement et de recherche de services web dvelopps et publis par des entreprises. UDDI fournit des informations sur l'auteur de services web (adresse, contact...), sur la classification (socit informatique, hpital, ...) et sur les moyens techniques permettant de les invoquer. WSDL (Web Service Description Language) : dfinit l'interface du service web, les donnes et les types des messages, les interactions et les protocoles. Exprim sous forme de document XML, il est utilis pour dcrire : a. Le contrat de services : l'ensemble des oprations disponibles, ainsi que la structure des messages XML changs (exprime en XML Schema). b. Comment, concrtement, transporter les messages XML sur un ou plusieurs protocole (habituellement SOAP). c. La localisation des services. SOAP (Simple Object Access Protocol) : est un protocole d'encodage des messages reposant sur les technologies XML. Il dfinit une enveloppe pour la communication des services web. SOAP permet la transmission de messages entre objets distants, ce qui veut dire qu'il autorise un objet invoquer des mthodes d'objets physiquement situs sur une autre machine. Les messages sont changs l'aide du protocole de transport. Bien que HTTP (Hyper Text Transfert Protocol) soit le plus utilis, d'autres comme SMTP ou JMS sont galement possibles. XML (Extensible Markup Language) est la base sur laquelle sont construits et dfinis les services web (SOAP, WSDL et UDDI). L'intrt des services web rside dans la robustesse du protocole HTTP et donc sa capacit passer plus facilement les pare-feu qu'un autre protocole.

Offrir un service web


Pour offrir un service web avec SOAP il nous faut : Un fichier dcrivant le service ( Les services distants sont dcrits dans un fichier WSDL). Un programme ralisant le service (Objets distants EJB ou Servlet sur le serveur d'applications).

UDDI
Les programmes qui interagissent avec un autre via le Web doivent pouvoir trouver les informations leur permettant de s'interconnecter. UDDI fournit pour cela une approche standardise permettant de trouver les informations sur un service web et sur la faon de l'invoquer. UDDI est une base de registres de services web en XML, un peu comme les professionnels peuvent enregistrer leurs services dans les pages jaunes. Cet enregistrement inclut le type du mtier, sa localisation gographique, le site web, le numro de tlphone, etc. Les autres mtiers peuvent ensuite parcourir cette base et retrouver les informations sur un servcie web spcifique, qui contiennent des mtadonnes supplmentaires dcrivant son comportement et son emplacement. Ces informations sont stockes sous la forme de document WSDL : les clients peuvent lire ce document afin d'obtenir l'information et invoquer

le service.

WSDL
La base de registre UDDI pointe vers un fichier WSDL sur Internet, qui peut tre tlcharg par les consomateurs potentiels. WSDL est un langage de dfinition d'interfaces permettant de dfinir les interactions entre les consomateurs et les services. C'est donc le composant central d'un service web puiqu'il dcrit le type de message, le port, le protocole de communication, les oprations possibles, son emplacement et ce que le client peut en attendre. Vous pouvez considrer WSDL comme une interface Java, mais crite en XML.

Pour garantir l'interoprabilit, l'interface standard du service web doit tre standardise, afin qu'un consomateur et un producteur puissent partager et comprendre un message. C'est le rle de WSDL. SOAP, de son ct, dfinit la faon dont le message sera envoy d'un ordinateur l'autre. En ralit, WSDL est scind en deux parties que nous appelons abstraite et concrte. La signature du service, ses mthodes et ses paramtres sont dcrits de manire abstraite. Cette partie est ensuite lie un protocole de communication et un format de messages concrets. Ainsi, la partie abstraite est totalement dcouple de la manire concrte permettant d'appeler le service. Extrait d'un fichier WSDL du service Web que nous allons mettre en oeuvre : <?xml version="1.0" encoding="UTF-8"?> <definitions targetNamespace="http://photos/" name="StockerPhotosService"> <types> <xsd:schema> <xsd:import namespace="http://photos/" schemaLocation="http://portable:8080/StockerPhotosService/StockerPhotos?xsd=1" /> </xsd:schema> </types> <message name="stocker"> <part name="parameters" element="tns:stocker"></part> </message> ... <service name="StockerPhotosService"> <port name="StockerPhotosPort" binding="tns:StockerPhotosPortBinding"> <soap:address location="http://portable:8080/StockerPhotosService/StockerPhotos"></soap:address> </port> </service> </definitions> Cet extrait de document WSDL commence par l'en-tte <definitions>. Cet lment peut prendre plusieurs attributs facultatifs qui dfinissent des noms de domaines dans la suite du document. Dans notre exemple, la dfinition reoit le nom StockerPhotosService. Le service web portant ce mme nom (<service>) peut tre invoqu partir de l'URL spcifie (http://portable:8080/StockerPhotosService/StockerPhotos) Nous ne nous attarderons pas sur WSDL car, comme vous le dcouvrirez plus loin, ce document est gnr automatiquement et n'a pas tre dvelopp manuellement.

SOAP
SOAP est le protocole standard des services web. Il fournit le mcanisme de communication permettant de connecter les services qui changent des donnes au format XML au moyen du protocole rseau - HTTP le plus souvent. Comme WSDL, SOAP repose fortement sur XML : un message SOAP est un document XML contenant plusieurs lments (une enveloppe, un corps, etc. ). SOAP permet ainsi la transmission de messages entre objets distants, en invoquant des mthodes sur des objets physiquement situs sur une autre machine. Le transfert se fait le plus souvent l'aide du protocole HTTP.
Le protocole SOAP se dcompose en deux parties :

Une enveloppe, contenant des informations sur le message lui-mme afin de permettre son acheminement et son traitement ; Un modle de donnes, dfinissant le format du message, c'est--dire les informations transmettre. Voici deux captures de trames qui visualisent l'appel d'une mthode stocker() avec sa rponse.

JAXB
Les services web envoient des requtes et des rponses en changeant des messages XML. En Java, il existe plusieurs API de bas niveau pour traiter les documents XML et les schmas XML. La spcification JAXB fournit un ensemble d'API et d'annotations pour reprsenter les documents XML comme des artfacts Java reprsentant des documents XML. JAXB facilite la dsrialisation des documents XML en objets et leur srialisation en documents XML. Mme si cette spcification peut tre utilise pour n'importe quel traitement XML, elle est fortement intgre aux services web.

Pour en savoir plus sur JAXB.

JAX-WS
JAX-WS dfinit un ensemble d'API et d'annotations permettant de construire et de consommer des services web en Java. Elle fournit les outils pour envoyer et recevoir des requtes de services web via SOAP en masquant la complexit du protocole. Ni le consommateur ni le service n'ont donc besoin de produire ou d'analyser des messages SOAP car JAX-WS s'occupe de traitement de bas niveau. JAX-WS dpend lui-mme d'autres spcifications comme JAXB que nous venons de dcouvrir. JAX-WS est la nouvelle appellation de JAX-RPC (Java API for XML Based RPC) qui permet de dvelopper trs simplement des services web. JAX-WS fournit un ensemble d'annotations pour mapper la correspondance Java-WSDL. Il suffit pour cela d'annoter directement les classes Java qui vont reprsenter le service web. En ce qui concerne le client, JAX-WS permet d'utiliser une classe proxy pour appeler un service distant et masquer la complexit du protocole. Ainsi, ni le client ni le serveur n'ont besoin de gnrer ou de parser les messages SOAP. JAX-WS s'occupe de ces traitements de bas niveau. Dans l'exemple ci-dessous, une classe Java utilise des annotations JAX-WS qui vont permettre par la suite de gnrer le document WSDL. Le document WSDL est auto-gnrer par le serveur d'application au moment du dploiement :

@WebService() public class StockerPhotos { @WebMethod public void stocker(String nomFichier, byte[] octets) { ... } ... }

Appel d'un service Web


Malgr tous ces concepts, spcifications, standards et organisations, l'criture et la consommation d'un service web sont trs trs simples. Le code suivant, par exemple, prsente le code d'un service web qui s'occupe de la gestion du personnel :

session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.jws.WebService; import javax.persistence.*; @WebService @Stateless @LocalBean public class GestionPersonnel { @PersistenceContext private EntityManager bd; public void nouveau(Personne personne) { bd.persist(personne); } public Personne rechercher(Personne personne) { return bd.find(Personne.class, personne.getId()); } public void modifier(Personne personne) { bd.merge(personne); } public void supprimer(Personne personne) { bd.remove(rechercher(personne)); } public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Comme les entits ou les EJB, un service web utilise le modle de classe annot avec une politique de configuration par exception. Si tous les choix par dfaut vous conviennent, ceci signifie qu'un service web peut se rduire une simple classe Java annote @javax.ws.WebService. Le service GestionPersonnel propose plusieurs mthodes pour grer l'ensemble du personnel, savoir nouveau(), rechercher(), modifier(), supprimer() et listePersonnels(). Un objet Personne est chang entre le consommateur et le service web. Lorsque nous avons dcrit l'architecture d'un service web, nous avons vu que les donnes changes devaient tre des documents XML : nous avons donc besoin d'une mthode pour transformer un objet Java en XML et c'est l que JAXB entre en jeu avec ses annotations et son API. L'objet Personne doit simplement tre annot par @javax.xml.bind.annotation.XmlRootElement pour que JAXB le transforme en XML et rciproquement.

Grce aux annotations JAXB, il n'est pas ncessaire d'crire de code de bas niveau pour effectuer l'analyse XML car elle se produit en coulisse - le service web et le consomateur manipulent un objet Java. Le consommateur peut tre une classe Java qui cre une instance de Personne puis invoque le service web, comme dans le code suivant :

personnel.Client.java
package personnel; import java.awt.*; import java.awt.event.*; import javax.swing.*; import ws.*; public class Client extends JFrame { private JToolBar outils = new JToolBar(); private JTextField nom = new JTextField(); private JTextField prnom = new JTextField(); private JFormattedTextField ge = new JFormattedTextField(0); private JComboBox tlphones = new JComboBox(); private JPanel panneau = new JPanel(); private JComboBox liste = new JComboBox(); private static GestionPersonnel gestion; private Personne personne = new Personne(); private boolean effacer = true; public Client() { super("Personne"); add(outils, BorderLayout.NORTH); outils.add(new AbstractAction("Enregistrer") { public void actionPerformed(ActionEvent e) { personne = new Personne(nom.getText(), prnom.getText(), (Integer)ge.getValue()); for (int i=0; i<tlphones.getItemCount(); i++) personne.getTelephones().add((String) tlphones.getItemAt(i)); gestion.nouveau(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Ajout tlphone") { public void actionPerformed(ActionEvent e) { String tlphone = (String) tlphones.getSelectedItem(); tlphones.addItem(tlphone); } }); outils.add(new AbstractAction("Modifier") { public void actionPerformed(ActionEvent e) { personne.modifier(nom.getText(), prnom.getText(), (Integer)ge.getValue()); gestion.modifier(personne); listingPersonnes();

} }); outils.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { gestion.supprimer(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Effacer") { public void actionPerformed(ActionEvent e) { personne = new Personne(); rafrachir(); } }); panneau.setLayout(new GridLayout(4, 2)); panneau.add(new JLabel("Nom :")); panneau.add(nom); panneau.add(new JLabel("Prnom :")); panneau.add(prnom); panneau.add(new JLabel("ge :")); panneau.add(ge); panneau.add(new JLabel("Tlphones :")); panneau.add(tlphones); tlphones.setEditable(true); add(panneau); add(liste, BorderLayout.SOUTH); liste.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) { personne = (Personne) liste.getSelectedItem(); rafrachir(); } } }); listingPersonnes(); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void listingPersonnes() { effacer = true; liste.removeAllItems(); effacer = false; for (Personne personne : gestion.listePersonnels()) liste.addItem(personne); } private void rafrachir() { nom.setText(personne.getNom()); prnom.setText(personne.getPrenom()); ge.setValue(personne.getAge()); tlphones.removeAllItems(); for (String tlphone : personne.getTelephones()) tlphones.addItem(tlphone); } public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } } Remarquez que dans la mthode main(), le consommateur n'invoque pas directement le service GestionPersonnel : il utilise une classe GestionPersonnelService et appelle la mthode getGestionPersonnelPort() afin d'obtenir une rfrence (gestion) un GestionPersonnel. Grce elle, il peut ensuite appeler les diffrentes mthodes du service web : nouveau(), modifier(), supprimer() et listePersonnels(). Bien que ce code soit trs simple comprendre, beaucoup de choses se passent en arrire-plan. Pour que tout ceci fonctionne, plusieurs artfacts ont t gnrs automatiquement : un fichier WSDL et des stubs clients qui contiennent toutes les informations pour se connecter l'URL de service web, la srialisation de l'objet Personne en XML, l'appel du service web et la rcupration des diffrents rsultats. La partie visible des services web en Java ne manipule pas directement XML, SOAP ou WSDL. Elle est donc trs simple comprendre. Cependant, certaines parties invisibles sont trs importantes pour l'interoprabilit.

JAXB : (Java Architecture for XML Binding)


Comme vous l'avez compris, XML est utilis pour changer les donnes et dfinir les services web via WSDL et les enveloppes SOAP. Pourtant, dans le code prcdent, un consommateur invoquait un service web sans qu'il n'y ait aucune trace de XML car ce consommateur ne manipulait que des interfaces et des objets Java distants qui, leur tour, grent toute la plomberie XML et les connexions rseau. Nous manipulons des classes Java un endroit de la chane et des documents XML un autre - le rle de JAXB est de faciliter cette correspondance bidirectionnelle. J'ai dj consacr toute une tude sur JAXB. Vous pouvez vous y reporter. Toutefois, lors de ce chapitre, nous allons nous attacher uniquement certain aspects de cette technologie que je prfre vous reprciser ici. JAXB dfinit un standard permettant de lier les reprsentations Java XML et rciproquement. Il gre les documents XML et les dfinitions des schmas XML (XSD) de faon transparente et oriente objet qui masque la complexit du langage XSD. Mise part l'annotation @XmlRootElement, le code suivant est celui d'une classe (ou d'une entit) normale.

Grce cette simple annotation et un mcanisme de srialisation, JAXB est capable de crer une reprsentation XML d'une instance de Personne.

Document XML reprsentant les donnes d'un personnel de l'entreprise


<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <personne> <id>51</id> <nom>REMY</nom> <prenom>Emmanuel</prenom> <age>71</age> <telephones>05-78-96-45-45</telephones> <telephones>06-45-87-85-21</telephones> <telephones>04-89-77-11-42</telephones> </personne> La srialisation, ici, consiste transformer un objet en XML, mais JAXB permet galement de faire l'inverse : la dsrialisation qui prend ce document XML en entre et cre un objet Personne partir des valeurs de ce document. JAXB peut produire automatiquement le schma qui valide automatiquement la structure XML du personnel afin de garantir qu'elle est correcte et que les types des donnes conviennent.

Schma XML validant le document XML prcdent


<?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.1-hudson-28-. --> <xs:schema xmlns:tns="http://session/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://session/"> <xs:element name="personne" type="tns:personne" /> <xs:complexType name="personne"> <xs:sequence> <xs:element name="age" type="xs:int" /> <xs:element name="id" type="xs:long" /> <xs:element name="nom" type="xs:string" minOccurs="0" /> <xs:element name="prenom" type="xs:string" minOccurs="0" /> <xs:element name="telephones" type="xs:string" nillable="true" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> </xs:schema> Le document ci-dessus montre le schma XML (XSD) de la classe Personne. Ce schma est constitu uniquement d'lments simples. Vous remarquez que tous les marqueurs sont prfixs par xs (xs:int, xs:long, xs:string, etc.) : ce prfixe est un espace de noms et est dfini dans l'lment xmlns (XML namespace) du marqueur d'entte du document. Les espaces de noms crent des prfixes uniques pour les lments des documents ou des applications qui sont utiliss ensemble. Leur but principal consiste viter les conflits qui pourraient survenir lorsqu'un mme nom d'lment apparat dans plusieurs documents. Ceci peut poser un problme non ngligeable pour les servcies web car ils manipulent plusieurs documents en mme temps (l'enveloppe SOAP, le document WSDL, etc.) : les espaces de noms sont donc trs importants pour les services web.

Liaison
L'API JAXB, dfinie dans le paquetage javax.xml.bind, fournit un ensemble d'interfaces et de classes permettant de produire des documents XML et des classes Java - en d'autres termes, elle relie les deux modles. Le framework d'excution de JAXB implmente les oprations de srialisation et de dsrialisation. 1. La srialisation (ou marshalling) consiste convertir les instances des classes annotes par JAXB en reprsentations XML. 2. Inversement, la dsrialisation (unmarshalling) consiste convertir une reprsentation XML en arborescence d'objets.

Les donnes XML srialises peuvent tre valides par un schma XML - JAXB peut produire automatiquement ce schma partir d'un ensemble de classes et vice versa. JAXB fournit galement un compilateur de schmas (xjc) et un gnrateur de schma (schemagen) - alors que la srialisation/dsrialisation manipule des objets et des documents XML, ce compilateur et ce gnrateur de schmas manipulent des classes et des schmas XML.

Annotation
Par bien des aspects, JAXB ressemble JPA. Cependant, au lieu de faire correspondre les objets une base de donnes, JAXB les lie un document XML. Comme JPA, JAXB dfinit un certain nombre d'annotations (dans la paquetage javax.xml.bind.annotation) afin de configurer cette association et s'appuie sur la configuration par exception pour allger le travail du dveloppeur.

Comme le montre le code suivant, l'quivalent de @Entity des objets persistants est l'annotation @XmlRootElement de JAXB :

Une classe Personne personnalise


@XmlRootElement public class Personne { private long id; private String nom; private String prenom; private int age; ... } L'annotation @XmlRootElement prvient JAXB que la classe Personne est l'lment racine du document XML. Si cette annotation est absente, JAXB lancera une exception lorsqu'il tentera de srialiser la classe. Cette dernire est ensuite associe au schma ci-dessous en utilisant les associations par dfaut (chaque attribut est traduit en un lment de mme nom)

Schma XML correspondant la classe prcdente


<?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.1-hudson-28-. --> <xs:schema xmlns:tns="http://session/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://session/"> <xs:element name="personne" type="tns:personne" /> <xs:complexType name="personne"> <xs:sequence> <xs:element name="age" type="xs:int" /> <xs:element name="id" type="xs:long" /> <xs:element name="nom" type="xs:string" minOccurs="0" /> <xs:element name="prenom" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:schema>

A l'aide du marshalling, nous obtenons aisment une reprsentation XML d'un objet de type Personne. L'lment racine <personne> reprsente l'objet Personne et inclut la valeur de chaque attribut de la classe.

Document XML reprsentant les donnes d'un personnel de l'entreprise


<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <personne> <id>51</id> <nom>REMY</nom> <prenom>Emmanuel</prenom> <age>31</age> </personne> JAXB permet de personnaliser et de contrler cette structure. Un document XML tant compos d'lments (<element>valeur</element>) et d'attributs (<element attribute="valeur" />), JAXB utilise deux annotations pour les diffrencier : @XmlElement et @XmlAttribute. Chacune d'elles reconnat un certain nombre de paramtres permettant de renommer un attribut, d'autoriser ou non les valeurs null, d'attribuer des valeurs par dfaut, etc. Le code ci-dessous utilise ces annotations spcifiques pour transformer le numro d'identifiant en attribut (au lieu d'un lment par dfaut) et pour renommer l'ensemble des balises du document XML.

Une classe Personne personnalise


@XmlRootElement public class Personne { @XmlAttribut(required = true) private long id; @XmlElement(name = "Nom") private String nom; @XmlElement(name = "Prnom") private String prenom; @XmlElement(name = "ge", defaultValue = "21") private int age; ... } Cette classe sera donc lie un schma diffrent dans lequel le numro d'identifiant est un <xs:attribute> obligatoire et l'ge renomm possde une valeur par dfaut de 21.

Schma XML correspondant la classe prcdente


<?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.1-hudson-28-. --> <xs:schema xmlns:tns="http://session/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://session/"> <xs:element name="personne" type="tns:personne" /> <xs:complexType name="personne"> <xs:sequence> <xs:element name="ge" type="xs:int" default="21" /> <xs:element name="Nom" type="xs:string" minOccurs="0" /> <xs:element name="Prnom" type="xs:string" minOccurs="0" /> </xs:sequence> <xs:attribute name="id" type="xs:long" use="required" /> </xs:complexType> </xs:schema>

Document XML reprsentant les donnes d'un personnel de l'entreprise


<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <personne id="51"> <Nom>REMY</Nom> <Prnom>Emmanuel</Prnom> <ge>21</ge> </personne> Le tableau ci-dessous numre les principales annotations de JAXB ; la plupart avec les lments auxquels elles s'appliquent ; certaines peuvent annoter des attributs, d'autres des classes et certaines peuvent s'appliquer tout un paquetage (@XmlSchema, par exemple). Annotation XmlAccessorOrder XmlAccessorType XmlAnyAttribute XmlAnyElement XmlAttachmentRef XmlAttribute XmlElement XmlElementDecl XmlElementRef XmlElementRefs XmlElements XmlElementWrapper XmlEnum XmlEnumValue XmlID XmlIDREF XmlInlineBinaryData XmlList XmlMimeType XmlMixed XmlNs XmlRegistry Description Contrler l'ordre des attributs et des proprits dans la classe. Contrler si l'attribut ou la proprit de la classe est srialis par dfaut. Convertir une proprit de la classe en un ensemble d'attributs de type jocker dans le document XML. Convertir une proprit de la classe en une description representant l'lment JAXB dans le document XML. Marquer un attribut ou une proprit de la classe comme une URI que fait rfrence une type MIME particulier. Convertir une proprit de la classe en un attribut dans le document XML. Convertir une proprit de la classe en un lment dans le document XML. Associer une fabrique un element XML. Convertir une proprit de la classe en un lment dans le document XML qui est associ un nom de proprit particulier. Marquer une proprit de la classe qui fait rfrence aux classes qui possdente XmlElement ou JAXBElement. Crer un conteneur pour de multiples annotations XmlElement, qui prcise ainsi le choix possible parmi celles qui sont proposes. Crer un lment pre dans le document XML pour des collections d'lments. Convertir une numration en une reprsentation quivalente dans le document XML. Convertir un numrateur en une reprsentation quivalente dans le document XML. Convertir une proprit de la classe en un ID XML. Convertir une proprit de la classe en un IDREF XML. Encoder en base64 dans le document XML. Utilis pour convertir une proprit de la classe vers une liste de type simple. Associer un type MIME qui contrle la reprsentation XML en rapport avec la proprit de la classe. Annoter une proprit de la classe qui peut supporter plusieurs types de valeur avec un contenu mixte. Associer un prefix d'un espace de nommage une URI. Marquer une classe comme possdant une ou des mthodes annotes avec XmlElementDecl.

XmlRootElement XmlSchema XmlSchemaType XmlSchemaTypes XmlTransient XmlType XmlValue

Associer une classe ou une numration un element XML. Trs souvent utilis pour spcifier la racine du document XML. Associer un espace de nommage un paquetage. Associer un type Java ou une numeration un type dfini dans un schma. Conteneur pour plusieurs proprits annotes par XmlSchemaType. Marquer une entit pour qu'elle ne soit pas mappe dans le document XML. Convertir une classe ou une numration vers le type spcifi dans Shma XML correspondant. Mapper une classe vers le type complexe dans le Schma XML ou vers le type simple suivant le cas.

La partie immerge de l'iceberg


Mme si nous ne manipulons pas explicitement des documents SOAP et WSDL lorsque nous dveloppons avec JAX-WS, il est important de comprendre un peu leur structure. Les services web fournissent deux ingrdients essentiels : Un langage de dfinition d'interface (WSDL) : Lorsque nous dveloppons des services web, nous pouvons nous servir de WSDL pour dfinir les paramtres d'entre et de sortie du service en termes de schma XML. Un standard de messages (SOAP) : Les messages SOAP, quant eux, permettent de transporter les paramtres d'entre et de sortie prciss par le document WSDL. Lorsqu'un consommateur invoque le service web GestionPersonnel, il rcupre son WSDL pour connatre son interface et il demande connatre la liste de tous les agents (le message listePersonnels de SOAP) et reoit une rponse (le message listePersonnelsResponse de SOAP).

WSDL
Les documents WSDL sont hbergs dans le conteneur de service web et utilisent XML pour dcrire ce que fait un service, comment appeler ses oprations et o le trouver. Ce document XML respecte une structure bien tablie, forme de plusieurs parties. Le sercice GestionPersonnel, par exemple, utilise les lments suivants : <definition> : est l'lment racine de WSDL. Il prcise les dclarations des espaces de noms visibles dans tout le document. <types> : dfinit les types de donnes utiliss par les messages. Ici, c'est la dfinition du schma XML (GestionPersonnel?xsd=1) qui dcrit le type des paramtres de la requte adresse au service (un objet GestionPersonnel) et le type de la rponse (List<Personne>). <message> : dfinit le format des donnes changes entre le consommateur du service web et le service web lui-mme. Ici, il s'agit de la requte (la mthode listePersonnels) et de la rponse (listePersonnelsResponse). <portType> : prcise les oprations du service (la mthode nouveau par exemple). <binding> : dcrit le protocole concret (SOAP, ici) et les formats des donnes pour les oprations et les messages dfinis pour un type de port particulier. <service> : contient une collection d'lments <port> associs, chacun une extrmit (une adresse rseau ou une URL).

Fichier WSDL partiel pour le service web GestionPersonnel


<?xml version='1.0' encoding='UTF-8'?> xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://session/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://session/" name="GestionPersonnelService"> <types> <xsd:schema> <xsd:import namespace="http://session/" schemaLocation="http://214-0:8080/GestionPersonnelService/GestionPersonnel?xsd=1" /> </xsd:schema> </types> <message name="modifier"> <part name="parameters" element="tns:modifier" /> </message> <message name="modifierResponse"> <part name="parameters" element="tns:modifierResponse" /> </message> <message name="nouveau"> <part name="parameters" element="tns:nouveau" /> </message> <message name="nouveauResponse"> <part name="parameters" element="tns:nouveauResponse" /> </message> <portType name="GestionPersonnel"> <operation name="modifier"> <input wsam:Action="http://session/GestionPersonnel/modifierRequest" message="tns:modifier" /> <output wsam:Action="http://session/GestionPersonnel/modifierResponse" message="tns:modifierResponse" /> </operation> <operation name="nouveau"> <input wsam:Action="http://session/GestionPersonnel/nouveauRequest" message="tns:nouveau" /> <output wsam:Action="http://session/GestionPersonnel/nouveauResponse" message="tns:nouveauResponse" /> </operation> </portType> <binding name="GestionPersonnelPortBinding" type="tns:GestionPersonnel">

<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="modifier"> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_modifier_WSAT_Policy" /> <soap:operation soapAction="" /> <input> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_modifier_WSAT_Policy" /> <soap:body use="literal" /> </input> <output> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_modifier_WSAT_Policy" /> <soap:body use="literal" /> </output> </operation> <operation name="nouveau"> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_nouveau_WSAT_Policy" /> <soap:operation soapAction="" /> <input> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_nouveau_WSAT_Policy" /> <soap:body use="literal" /> </input> <output> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_nouveau_WSAT_Policy" /> <soap:body use="literal" /> </output> </operation> </binding> <service name="GestionPersonnelService"> <port name="GestionPersonnelPort" binding="tns:GestionPersonnelPortBinding"> <soap:address location="http://214-0:8080/GestionPersonnelService/GestionPersonnel" /> </port> </service> </definitions> L'lment <xsd:import namespace> fait rfrence un schma XML qui doit tre disponible sur le rseau pour les clients du WSDL. .

SOAP
Alors que WSDL dcrit une interface abstraite du service web, SOAP fournit une implmentation concrte en dfinissant la structure XML des messages changs. Dans le cadre du Web, SOAP est une structure de messages pouvant tre dlivrs par HTTP (ou d'autres protocoles de communication) - la liaison HTTP de SOAP contient quelques en-ttes d'extension HTTP standard. Cette structure de message est dcrite en XML. Au lieu d'utiliser HTTP pour demander une page web partir d'un navigateur, SOAP envoie un message XML via une requte HTTP et reoit une rponse HTTP. Un message SOAP est un document XML contenant les lments suivants : <Enveloppe> : Dfinit le message et l'espace de noms utiliss dans le document. Il s'agit de l'lment racine obligatoire. <Header> : Contient les attributs facultatifs du message ou l'infrastructure spcifique l'application, comme les informations sur la scurit ou le routage rseau. <Body> : Contient le message chang entre les applications. <Fault> : Fournit des informations sur les erreurs qui surviennent au cours du traitement du message. Cet lment est facultatif. Seuls l'enveloppe et le corps sont obligatoires. Dans notre exemple, une application cliente appelle le service web pour connatre l'ensemble du personnel de l'entreprise (une enveloppe SOAP pour la requte) et reoit la liste des agents (une autre enveloppe SOAP pour la rponse).

JAX-WS : Java API for XML-Based Web Services


Nous venons de dcouvrir un document WSDL, ainsi qu'une requte et une rponse SOAP. Vous avez pu remarquer que lorsque les services web proposent plusieurs oprations avec des pararamtres complexes, ces documents XML deviennent un vritable cauchemar pour le dveloppeur ; heureusement, JAX-WS leur facilite la vie en masquant cette complexit. Le document WSDL tant le contrat qui lie le consommateur et le service, il peut servir crire le code Java pour ces deux parties : c'est ce que nous appelons la mthode descendante, galement mthode par contrat car elle part du contrat (le WSDL) en dfinissant les oprations, les messages, etc. Lorsque le consommateur et le fournisseur sont d'accord sur le contrat, nous pouvons implmenter les classses en fonction de celui-ci. Metro fournit quelques outils permettant de produire des classes partir d'un document WSDL. Dans l'autre approche, la mthode ascendante, la classe de l'implmentation existe dj et il suffit de crer le WSDL. L encore, Metro dispose d'outils pour effectuer cette opration. Dans les deux cas, le code doit parfois tre ajust pour correspondre au WSDL ou vice versa, et c'est l que JAX-WS vient votre secours : grce un modle de dveloppement simple et quelques annotations, vous pouvez ajuster l'association Java-WSDL. L'approche ascendante peut produire des applications trs inefficaces car les mthodes et les classes Java n'ont aucune ide de la granularit idale des messages qui circulent sur le rseau. Si la latence est leve et/ou la bande passante, faible, il est prfrable d'utiliser moins de messages, qui seront plus gros : seule l'approche par contrat permet de faire ce choix. Toute cette tude nous a prsent les concepts et les spcifications des services web en gnral puis a introduit JAXB et a effleur la surface des documents WSDL et SOAP. Cependant, les services web suivent le paradigme de facilit de dveloppement de Java EE 6 et n'obligent pas crire le moindre code WSDL ou SOAP. Un service web est une simple classe annote qui doit tre dploye dans un conteneur de service web. Toutefois, ce modle de programmation mrite notre attention.

Le modle JAX-WS
Comme la plupart des composants de Java EE 6, les services web s'appuient sur le paradigme de la configuration par exception. Seule l'annotation @WebService est ncessaire pour transformer une simple classe en service web, mais cette classe doit respecter les rgles suivantes : Elle doit tre annote par @javax.jws.WebService. Pour transformer un service web en lment EJB, la classe doit tre annote par @javax.ejb.Stateless. Elle doit tre publique et ne doit pas tre finale ni abstraite. Elle doit possder un constructeur par dfaut public. Elle ne doit pas dfinir la mthode finalize(). Un service doit tre un objet sans tat et ne pas mmoriser l'tat spcifique d'un client entre les appels de mthode. Au niveau du service, les systmes sont dfinis en terme de messages XML, d'oprations WSDL et de messages SOAP. Cependant, au niveau Java, les applications sont dcrites en termes d'objets, d'interfaces et de mthodes. Il est donc ncessaire d'effectuer une traduction des objets Java vers les oprations WSDL. L'environnement d'excution de JAXB utilise les annotations pour savoir comment srialiser/dsrialiser une classe vers/ partir de XML. Gnralement ces annotations sont caches au dveloppeur du service web. De mme, JAX-WS se sert d'annotations pour dterminer comment srialiser un appel de mthode vers un message de requte SOAP et comment dsrialiser une rponse SOAP vers une instance du type du rsultat de la mthode. Il existe deux sortes d'annotations : Les annotations de traduction WSDL : Elles appartiennent au paquetage javax.jws et permettent de modifier les associations entre WSDL et Java. Les annotations @WebMethod, @WebResult, @WebParam et @OneWay sont utilises sur le service web pour adapter la signature des mthodes exposes. Les annotations de traduction SOAP : Elles appartiennent au paquetage javax.jws.soap et permettent d'adapter la liaison SOAP (@SOAPBinding et @SOAPMessageHandler).

@WebService
L'annotation @javax.jws.WebService marque une classe ou une interface Java comme tant un service web. Cette annotation possde un certain nombre d'attributs permettant de personnaliser le nom du services web dans le fichier WSDL (lments <wsdl:portType> ou <wsdl:service>) et son espace de noms ainsi que l'emplacement du fichier WSDL lui-mme (attribut wsdlLocation).

API de l'annotation @WebService


@Target(TYPE) @Retention(RUNTIME) public @interface WebService { public String name() default ""; public String targetNamespace() default ""; public String serviceName() default ""; public String portName() default ""; public String wsdlLocation() default ""; public String endpointInterface() default ""; } name : dfinit le nom du service web. Ce nom se trouve alors dans le fichier WSDL dans l'attribut name de la balise <portType>. La valeur par dfaut est le nom de la classe d'imlmentation, par exemple GestionPersonnel. serviceName : dfinit le nom du service. Ce nom se trouve dans le fichier WSDL dans l'attribut name de la balise <service>. La valeur par dfaut est le nom de la classe d'implmentation suivie du suffixe "Service", par exemple GestionPersonnelService. wsdlLocation : dfinit l'URL, qu'elle soit relative ou absolue, d'un fichier WSDL existant. Par dfaut, ce fichier est auto-gnr lors du dploiement. Lorsque nous utilisons @WebService, toutes les mthodes publiques du service web qui n'utilisent pas l'annotation @WebMethod sont exposes.

@WebMethod
Par dfaut, toutes les mthodes publiques d'un service web sont exposes dans le WSDL et utilisent toutes les rgles d'association par dfaut. L'annotation @javax.jws.WebMethod permet de personnaliser certaines de ces associations de mthodes. Son API est assez simple et permet de renommer une mthode ou de l'exclure du WSDL.

session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd; public void nouveau(Personne personne) { bd.persist(personne); } @WebMethod(exclude = true) public Personne rechercher(Personne personne) { return bd.find(Personne.class, personne.getId()); } public void modifier(Personne personne) { bd.merge(personne); } public void supprimer(Personne personne) { bd.remove(rechercher(personne)); } @WebMethod(operationName = "listeDuPersonnel") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Le service web ci-dessus exclut la mthode rechercher() et demande renommer la mthode listePersonnels(). .

@WebResult
L'annotation @javax.jws.WebResult fonctionne en relation avec @WebMethod pour contrler le nom de la valeur renvoye par le message dans le WSDL.

session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { ... @WebMethod(operationName = "listeDuPersonnel") @WebResult(name = "personnels") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Dans le code ci-dessus, le rsultat de la mthode listePersonnels() est renomme personnels. .

@WebParam
L'annotation @javax.jws.WebParam, dont l'API est prsente ci-dessous, ressemble @WebResult car elle personnalise les paramtres des mthodes du service web. Cette API permet de modifier le nom du paramtre dans le WSDL, l'espace de nom et le mode de passage des paramtres - IN, OUT, INOUT.

API de l'annotation @WebParam


@Target(TYPE) @Retention(RUNTIME) public @interface WebParam { public String name() default ""; public String targetNamespace() default ""; public enum Mode {IN, OUT, INOUT};

public boolean header() default false; public String partName() default ""; }

session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { ... @WebMethod public void nouveau(@WebParam(name = "agent") Personne personne) { bd.persist(personne); } ... }

@OneWay
L'annotation @OneWay peut tre utilise avec les mthodes qui ne renvoient aucun rsultat, comme celles de type void. Cette annotation ne possde aucun lment et peut tre considre comme une interface de marquage informant le conteneur que l'appel de cette mthode peut tre optimis (en utilisant un appel asynchrone, par exemple) puisqu'il n'y a pas de valeur de retour.

session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { ... @WebMethod @OneWay @Asynchronous public void nouveau(@WebParam(name = "agent") Personne personne) { bd.persist(personne); } ... }

Cycle de vie et mthode de rappel


Comme vous pouvez le constater avec la figure ci-dessous, le cycle de vie des services web ressemble celui des beans sans tat et des MDB. Comme avec tous les composants qui ne mmorisent pas l'tat, soit ils n'existent pas, soit ils sont prts traiter une requte. Ce cycle de vie est gr par le conteneur. Comme elles s'excutent dans un conteneur, les EJB autorisent l'injection de dpendances et les mthodes de rappel du cycle de vie : si elle existe, le conteneur appelera la mthode de rappel @PostConstruct lorsqu'il cre une instance d'un service web et la mthode de rappel @PreDestroy lorsqu'il la dtruit.

Contexte d'un service web


Un service web possde un contexte d'environnement auquel il peut accder en injectant une rfrence javax.xml.ws.WebServiceContext au moyen de l'annotation @Resource. Dans ce contexte, le service peut obtenir des informations d'excution comme la classe qui implmente l'extrmit, le contexte du message et des informations concernant la scurit relative la requte qui est traite.

session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { @Resource private WebServiceContext contexte; ... }
Mthode Description

getMessageContext() getUserPrincipal() isUserInRole() getEndPointReference()

Renvoie le MessageContext de la requte en cours de traitement au moment de l'appel. Permet d'accder aux en-ttes du message SOAP, etc. Renvoie le principal qui identifie l'metteur de la requte en cours de traitement. Teste si l'auteur authentifi appartient au rle logique indiqu. Renvoie l'EndPointReference associ cette extrmit.

Appel d'un service web


Vous pouvez invoquer un service web en utilisant le fichier WSDL et certains outils de gnration de classes Java relais (stubs). L'appel d'un service web ressemble l'appel d'un objet distribu avec RMI : comme ce dernier, JAX-WS permet au programmeur d'utiliser un appel de mthode local pour invoquer un service se trouvant sur un autre hte. La diffrence est que le service web sur l'hte distant peut tre crit dans un autre langage de programmation. Le fichier WSDL tablit le contrat entre le consommateur et le service, et Metro fournit un outil de conversion WSDL vers Java (wsimport) qui produit des classes et des interfaces partir du code WSDL - ces interfaces sont appeles SEI (service endpoint interfaces) car ce sont des reprsentations Java d'une extrmit de service web (servlet ou EJB). Cette SEI agit comme un proxy qui route l'appel Java local vers le service web distant via HTTP ou d'autres protocoles de transport. Lorsqu'une mthode de ce proxy est appel, elle convertit ses paramtres en message SOAP (la requte) et l'envoie l'extrmit du web service. Pour obtenir le rsultat, la rponse SOAP est reconvertie en une instance du type renvoy. Pour l'utiliser, il n'est pas ncessaire de connatre le fonctionnement interne du proxy ni d'tudier son code. Avant de compiler le consommateur client, il est ncessaire de produire la SEI afin d'obtenir la classe proxy pour l'appeler dans le code.

Le consommateur peut obtenir une instance du proxy par injection ou en la crant par programme. Nous injectons un client de service web l'aide de l'annotation @javax.xml.ws.WebServiceRef, qui ressemble aux annotations @Resource ou @EJB prsents dans les tudes prcdentes. Lorsqu'elle est applique un attribut, le conteneur injecte une instance du service web au moment o l'application est initialise.

personnel.Client.java
package personnel; ... public class Client extends JFrame { @WebServiceRef private static GestionPersonnelService service; private static GestionPersonnel gestion; ... public static void main(String[] args) { gestion = service.getGestionPersonnelPort(); new Client(); } ... gestion.nouveau(personne); gestion.modifier(personne); gestion.supprimer(personne); gestion.listePersonnels()) ... } La classe GestionPersonnelService est la SEI, pas le service web lui-mme. Vous devez ensuite obtenir la classe proxy GestionPersonnel afin de permettre localement l'invocation des mthodes mtiers. Nous appelons localement les mthodes nouveau(), modifier(), etc. du proxy, qui, leurs tours, invoquerons le service web distant, creront les requtes SOAP, srialiserons les messages, etc. Pour que cette injection fonctionne, ce code doit s'excuter dans un conteneur (de servlet, d'EJB ou de client d'application) : dans le cas contraire, nous ne pouvons pas utiliser l'annotation @WebServiceRef et nous devons alors passer imprativement par la programmation. Les classes produites par l'outil wsimport peuvent tre directement utilises. Il suffit alors tout simplement de crer une instance de GestionPersonnelService l'aide de l'oprateur new ; le reste est entirement identique. Du coup, l'injection peut ne pas paratre trs intressante si ce n'est le fait d'avoir l'application cliente automatiquement installe au travers de Java Web Start. Les outils wsimport et wsgen sont founis avec le JDK 1.6. Vous pouvez accder directement ces outils ou passer par l'interface en ligne de commande de Glassfish. wsimport prend en entre un fichier WSDL et produit les artefacts JAX-WS, une SEI notamment. wsgen lit une classe extrmit de service web et produit le WSDL.

personnel.Client.java
package personnel; ... public class Client extends JFrame { private static GestionPersonnel gestion; ... public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } ... gestion.nouveau(personne); gestion.modifier(personne); gestion.supprimer(personne); gestion.listePersonnels()) ... }

Conclusion
Le dveloppement et l'utilisation d'un service web comporte quatre phases : Dveloppement du service web proprement dit. Gnration des artefacts ct serveur (wsgen). Gnration des artefacts ct client (wsimport). Appel du service web chez le client.

Projet Archivage de photos


Nous allons maintenant valider toutes ces notions apprises au cours de cette tude au travers de deux projets. Le premier concerne l'archivage de photos sur un serveur distant. Le deuxime, qui sera trait dans un chapitre part entire permettra de grer l'identit du personnel d'une entreprise. Ce premier projet d'archivage englobe en ralit deux parties : D'une part un module EJB qui consiste en la ralisation du service web qui s'occupe d'archiver correctement les photos envoyes sur le serveur distant. D'autre part, nous allons mettre en oeuvre une application cliente qui va utiliser ce service web et dont l'objectif est de slectionner les photos intressantes sur le poste local afin de les archiver ventuellement sur le serveur distant. Par ailleurs, bien entendu, il est possible de rcuprer ces photos archives afin de les replacer sur le poste local.

Finalement l'ensemble de ce projet global va tre constitu de deux sous projets correspondant deux postes informatiques diffrents, c'est--dire deux machines virtuelles galement diffrentes.

Diagramme de dploiement

Diagramme de cas d'utilisation

Module EJB - Cration du service web


Procdons par ordre et commenons par crer le service web qui va tre capable d'archiver les photos dsires sur le disque dur du serveur. Le point d'extrmit est un bean session. Du coup, il est ncessaire de prvoir un projet de type "Module EJB". Le nom du projet s'intitule ArchiverPhotos qui possde un bean session Archiver, de type stateless, dont voici le code :

session.Archiver.java
package session; import java.io.*; import javax.ejb.*; import javax.jws.*; @WebService @Stateless public class Archiver { private final String rpertoire = "C:/Archivage/"; @Asynchronous public void stocker(String nom, byte[] octets) throws IOException { File fichier = new File(rpertoire+nom); if (fichier.exists()) return; FileOutputStream photo = new FileOutputStream(fichier); photo.write(octets); photo.close();

} public byte[] restituer(String nom) throws IOException { File fichier = new File(rpertoire+nom); if (!fichier.exists()) return null; FileInputStream photo = new FileInputStream(fichier); byte[] octets = new byte[(int)fichier.length()]; photo.read(octets); photo.close(); return octets; } public String[] liste() { return new File(rpertoire).list(); } public void supprimer(String nom) { new File(rpertoire+nom).delete(); } } Ce code est finalement extrmement simple puisque toute la problmatique rseau est totalement occulte par le fait que nous utilisons un bean session (objet distant). Par ailleurs, la mise en oeuvre du service web est encore plus simple puisqu'il suffit juste de proposer la seule annotation @WebService directement sur la classe reprsentant le bean session. Remarquez au passage que la mthode stocker() est asynchrone, ce qui permet de librer l'application cliente ds que l'envoi des octets est ralis. Chacun reprend la main de son ct, et le serveur peut ainsi, pendant ce temps l, crer le fichier image correspondant dans le rpertoire de stockage ddi. Une fois que ces quelques lignes sont introduites, dans netbeans pas exemple, vous pouvez demander l'excution de ce service (un simple clic sur le bouton Run) qui dans l'ordre propose : la compilation et l'archivage suivie du dploiement sur le serveur d'applications Glassfish. Ce dernier gnre alors effectivement le service web avec les diffrents artefacts ncessaires ( l'aide de l'utilitaire interne wsgen) ainsi que le contrat du service sous forme de document WSDL.

Je rappelle que les artefacts sont des classes spcialises qui s'occupent uniquement, soit de gnrer des documents XML en relation avec la rponse du service souhait, soit d'tre capable de remettre en forme une requte sous forme d'appel de mthode (avec les arguments ncessaires) partir d'un document XML envoy par le client. Vous pouvez consulter tout moment notre service web directement sur le serveur Glassfish l'aide de l'application web propose par dfaut, et donc l'aide d'un simple navigateur. Nous retrouvons ainsi notre module EJB ArchiverPhotos l'intrieur duquel nous dcouvrons notre bean session Archiver. Il est alors possible de consulter par la suite le document WSDL dfinissant le service web.

Raliser un service web avec Netbeans et Glassfish est vraiment trs simple et trs rapide. Tout se fait en coulisse. Aprs avoir mis l'annotation @WebService sur un bean session sans tat (seul possibilit), il suffit ensuite de demander l'excution pour que le dploiement et la mise en service totale se ralise automatiquement, et ceci dans les plus brefs dlais.

Application cliente - Utilisation du service web distance


Nous pouvons ds lors nous consacrer l'application cliente qui va utiliser ce service web. Il s'agit d'une simple application fentre. L'avantage, c'est que cette application peut trs bien tre utilise depuis Internet puisque le protocole utilis est le protocole HTTP sur le port 80 (ici 8080) qui est gnralement non filtr par le parefeu. Nous devons donc laborer un nouveau projet pour une application Java classique. Avant d'crire le code relatif la mise en oeuvre de l'IHM, la toute premire chose s'occuper est de gnrer les artefacts ct client ( l'aide de wsimport) afin que nous disposions de toutes les classes ncessaires au dialogue avec le service web au travers du protocole SOAP/XML. Ces classes, je le rappelle, vont s'occuper de traduire ou de gnrer des documents XML. Ceci dit, encore une fois, il ne sera pas ncessaire de manipuler directement wsimport. Netbeans le fait votre place. Voici la procdure suivre. Il suffit de faire appel au menu WebServiceClient et de spcifier alors la localisation du document WSDL afin que les artefacts soient en adquation avec le service web utilis.

Ds que vous validez votre choix, wsimport est automatiquement excut et gnre donc votre place l'ensemble des classes ncessaires la communication avec le web service au travers du protocole SOAP / XML.

Maintenant tout est prt, nous pouvons donc crire notre codage Java correspondant toute la partie graphique afin de permettre la slection des photos prsentes sur le disque dur local et de les envoyer par la suite sur le disque dur du serveur par le service web que nous venons de dcrire.

photos.Client.java
package photos; import java.awt.BorderLayout; import java.awt.event.*; import java.io.*; import javax.imageio.ImageIO; import javax.swing.*; import ws.*; public class Client extends JFrame { private JTabbedPane onglets = new JTabbedPane(); private JPanel panneauLocal = new JPanel(new BorderLayout()); private JPanel panneauServeur = new JPanel(new BorderLayout()); private JToolBar outilsLocal = new JToolBar(); private JToolBar outilsDistant = new JToolBar(); private JLabel photoLocale = new JLabel(); private JLabel photoDistante = new JLabel(); private JLabel description = new JLabel(); private JComboBox listePhotos = new JComboBox(); private JFileChooser slecteur = new JFileChooser(); private File fichier; private byte[] octets; private boolean effacer = true; private static Archiver archivage; // <----------------------------------------------------------------------------------------------------------------- accs au service web public Client() { super("Envoyer des photos"); add(onglets); onglets.add("Photos en local", panneauLocal); onglets.add("Photos distantes", panneauServeur); panneauLocal.add(outilsLocal, BorderLayout.NORTH); panneauLocal.add(new JScrollPane(photoLocale)); outilsLocal.add(new AbstractAction("Slectionner") { public void actionPerformed(ActionEvent e) { slecteur.setFileSelectionMode(JFileChooser.FILES_ONLY); if (slecteur.showOpenDialog(Client.this)==JFileChooser.APPROVE_OPTION) { fichier = slecteur.getSelectedFile(); photoLocale.setIcon(new ImageIcon(fichier.getPath())); } } }); outilsLocal.add(new AbstractAction("Envoyer") { public void actionPerformed(ActionEvent e) { if (fichier!=null) try { byte[] octets = new byte[(int) fichier.length()]; FileInputStream lecture = new FileInputStream(fichier); lecture.read(octets); lecture.close(); archivage.stocker(fichier.getName(), octets); // <------------------------------------------ appel de la mthode stocker() du service web listingPhotos();

} catch (Exception ex) { setTitle("Impossible d'envoyer le fichier"); } } }); panneauServeur.add(outilsDistant, BorderLayout.NORTH); panneauServeur.add(new JScrollPane(photoDistante)); panneauServeur.add(description, BorderLayout.SOUTH); outilsDistant.add(new AbstractAction("Restituer") { public void actionPerformed(ActionEvent e) { slecteur.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (slecteur.showSaveDialog(Client.this)==JFileChooser.APPROVE_OPTION) { try { fichier = new File(slecteur.getSelectedFile() + "/" +(String)listePhotos.getSelectedItem()); FileOutputStream fluxImage = new FileOutputStream(fichier); fluxImage.write(octets); fluxImage.close(); } catch (Exception ex) { setTitle("Problme pour restituer la photo en local"); } } } }); outilsDistant.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { archivage.supprimer((String)listePhotos.getSelectedItem()); // <------------------------ appel de la mthode supprimer() du service web listingPhotos(); } }); outilsDistant.add(listePhotos); listePhotos.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) try { octets = archivage.restituer((String) listePhotos.getSelectedItem()); // <------- appel de la mthode restituer() du service web ByteArrayInputStream fluxImage = new ByteArrayInputStream(octets); photoDistante.setIcon(new ImageIcon(ImageIO.read(fluxImage))); } catch (Exception ex) { setTitle("Problme pour rcuprer l'image du serveur"); } } }); listingPhotos(); setSize(500, 400); setLocationByPlatform(true); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void listingPhotos() { effacer = true; listePhotos.removeAllItems(); for (String photo : archivage.liste()) listePhotos.addItem(photo); // <----------------------------------- appel de la mthode liste() du service web effacer = false; if (listePhotos.getItemCount()>0) listePhotos.setSelectedIndex(0); } public static void main(String[] args) { archivage = new ArchiverService().getArchiverPort(); new Client(); } }

L'lment important est, bien entendu, l'interface Archiver. Elle reprsente l'objet distant qui ralise l'ensemble du service dsir. Il suffit juste de faire appel aux bonnes mthodes - stocker(), restituer(), supprimer() et liste() - de l'objet local relatif cette interface, qui joue le rle de proxy, pour obtenir la rponse aux requtes dsires.

Pour obtenir ce proxy, objet implmentant l'interface Archiver, nous devons passer par une instance de la classe ArchiverService et faire appel la mthode getArchiverPort(). Une fois que l'objet archivage reprsentant cette classe Archiver est cr, pour le client, l'utilisation du service est vraiment trs simple d'emploi. Il suffit de faire appel la mthode dsire. Encore une fois, toute la problmatique rseau est totalement occult. Il faut noter que ce proxy implmente bien entendu toutes les mthodes prvues par le service web, mais possde galement toutes les mthodes utiles supplmentaires pour communiquer correctement avec le service web, en sous-marin, en utilisant les classes artefacts qui utilise les annotations JAXB. Ainsi, lorsque le client fait appel une mthode spcifique du service web, le proxy cre une instance de la classe artefact correspondante et ralise ainsi automatiquement le mapping Objet/XML qui va tre utile pour le protocole SOAP. Effectivement, ce sont les artefacts qui se proccupent d'envoyer les requtes encapsules dans un document XML sous une enveloppe SOAP et au travers du tube HTTP. La rponse la requte est galement un document XML qui est renvoy par le serveur, toujours en respectant le protocole SOAP. Ce document XML est ensuite transcrit (mapping Objet/XML) afin de rcuprer la rponse suivant le format attendu.

Principe des artefacts


Nous venons de le dire, l'utilisation d'un service web est vraiment trs simple et trs intuitive. Il suffit d'appeler la mthode qui vous intresse de l'objet distant (service web) en spcifiant les bonnes valeurs dans les arguments requis. Par contre, la communication entre le client et le service web se fait par l'intermdiaire de documents XML formats suivant le protocole SOAP. Les artefacts sont donc l pour se proccuper de la formation et de l'analyse de documents XML. Nous allons dtailler plus prcisment l'architecture de ces artefacts.

Pour chaque mthode du service web, nous disposons systmatiquement de deux artefacts, c'est--dire de deux classes, un pour la requte correspondant l'appel de la mthode, et un autre pour la valeur de retour de cette mthode. Le nom des classes artefacts correspond au nom de la mthode du service web. La premire classe correspondant la requte porte excatement le nom de la mthode avec toutefois, la premire lettre en majuscule. La deuxime classe est similaire la premire mais dispose en plus du suffixe Response. Ainsi donc, notre service web, propose quatre mthodes, respectivement : stocker(), restituer(), liste() et supprimer(). Nous avons donc notre dispositions les artefacts suivants : Stocker, StockerResponse, Restituer, RestituerResponse, Liste, ListeResponse et Supprimer, SupprimerResponse.

A chaque argument d'une mthode du service web correspond une proprit (un attribut, une mthode get et une mthode set) dans la classe artefact. Nous pourrions nous demander quoi servent ces proprits (avec donc systmatiquement un get et un set). Il faut bien comprendre que chaque artefact existe la fois ct client, mais galement ct serveur. Ainsi, lorsque nous voquons la mthode set d'un artefact d'un ct, de l'autre ct c'est la mthode get du mme artefact qui est invoqu, et vice versa.

Tous ces artefacts sont l pour raliser automatiquement des mapping Objet / XML. Dans le cas de la srialisation, l'application cliente par exemple cre une instance d'un artefact, par exemple Stocker, remplie les proprits avec les valeurs requises au moyen des mthode set. Ensuite, au moyen de JAXB, cet objet est transcrit en document XML correspondant. Le service web, grce au mme artefact, va raliser l'opration inverse, la dsrialisation, c'est--dire, toujours au moyen de JAXB, dcripter ce document XML envoy par le client au travers du rseau, et gnrer ainsi un objet, par exemple de type Stocker, qui est finalement une image parfaite de l'objet qui se trouve sur le client, et au moyen des mthodes get, cette fois-ci, le service web collecte les paramtres de la mthode du service web solliciter. Il est noter que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Lors de l'laboration du service web, nous avons plac la seule annotation @WebService, sans rajouter les annotations @WebMethod, @WebParam, etc. La consquence, c'est que le nom des proprits est un nom par dfaut. Ainsi, nous retrouverons systmatiquement les proprits arg0, arg1, etc. Visualisons ce sujet, ct client, l'interface Archiver reprsentant le service web. Notez que le nom des arguments n'est pas celui d'origine. Remarquez galement que le retour de la mthode liste() n'est plus de type String[], mais une List<String> :

Syntse de la communication entre l'application cliente et le web service


Nous allons maintenant conclure et bien visualiser au travers d'un cas d'utilisation, l'enchanement des communications entre le client et le service web au travers du tube HTTP en utilisant le protocole SOAP / XML.

Nous allons prendre le temps de bien dmontrer la communication entre les deux machines, l'aide d'un analyseur de trame, en faisant l'tude d'une mthode particulire du service web.

Diagramme de cas d'utilisation

Diagramme de squence de Stocker fichier

Pour archiver une photo dans le serveur, il est bien sr ncessaire, au pralable, de choisir la photo sur le poste local et de charger le fichier image correspondant en mmoire centrale. A partir de cette image, il est ensuite possible de collecter son nom de fichier et tout le contenu de la photo sous forme de tableau d'octets. Nous disposons maintenant de tous les lments ncessaires pour communiquer directement avec la mthode stocker() du service web (appel d'une mthode d'un objet distant, mme au travers d'Internet), ce qui est trs simple raliser. Toutefois, en sous-marin, avant d'arriver au service web, nous utilisons le proxy archivage qui implmente effectivement toutes les mthodes du service web, ct client, mais il est galement capable de gnrer des documents XML, respectant ainsi le protocole SOAP, qui vont tre envoy au travers du tube HTTP. Ainsi, lorsque nous faisons appel la mthode stocker() du proxy archivage, ce dernier gnre une partie du document XML envoyer simplement au moyen de la classe artefact Stocker. Cet artefact Stocker utilise effectivement les annotations JAXB. Dans un premier temps, le proxy cre un objet de cette classe, renseigne ensuite les attributs correspondants aux paramtres de la mthode stocker(), par les mthodes set, setArg0() et setArg1(). Une fois que cet objet est compltement renseign, le proxy, l'aide de JAXB, ralise ainsi un mapping Objet / XML (srialisation). Rappelez-vous que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Cet lment XML gnr, puisqu'il a t produit par la classe Stocker, rprsente ainsi la mthode stocker() avec ses deux paramtres, le nom du fichier d'une part et son contenu sous forme de tableau d'octets. Pour que le document dans son ensemble soit complet, Il faut proposer autour de cet lment, l'enveloppe SOAP correspondante, toujours en format XML, bien entendu. Une fois que le document XML est complet, il est enfin envoy au serveur sur le tube HTTP.

C'est le service web lui-mme qui collecte ce document, qui le dcripte tout de suite pour savoir quelle est la mthode qui est demande. C'est effectivement l'enveloppe qui fournit cette information. Puisque maintenant, la mthode du service est connue, il suffit de rcuprer la structure XML correspondante l'intrieur de cette enveloppe, et de solliciter JAXB, avec de nouveau la classe annote Stocker (ct serveur), mais en faisant l'opration inverse cette fois-ci, la dsrialisation, qui consiste faire un mapping XML / Objet. Grce JAXB, nous retrouvons l'objet complet reprsentant cette classe Stocker, avec donc le nom du fichier et bien sr tout son contenu sous forme de tableau d'octet. Ainsi, nous possdons exactement les mmes valeurs dans la classe Stocker du client et dans la classe Stocker du serveur. L'un est image de l'autre. Il ne reste plus qu' rcuprer chacune des valeurs importantes, savoir le nom du fichier et le tableau d'octets, au moyen respectivement des mthodes getArg0() et getArg1() de cette classe Stocker afin de permettre l'appel la mthode stocker() du service web. L aussi, nous retrouvons le mme appel de mthode stocker(), au niveau du proxy ct client, et bien sr au niveau du service web lui-mme. L'une est image de l'autre.

Par contre, tout le traitement effectif se fait bien entendu ct serveur. Ainsi, le fichier reprsentant la photo est finalement gnr dans le disque dur du serveur. Mme si la mthode stocker() ne possde pas de retour, toute la procdure inverse est propose et va servir d'accus rception. Ainsi, au travers de la classe StockerResponse, un document XML avec son enveloppe SOAP est renvoy au client. A titre d'exemple, je vous propose de voir galement les captures correspondant la demande de suppression d'une photo sur le serveur :

Capture de trames correspondant la demande de liste des fichiers images prsents actuellement sur le serveur :

Projet gestion du personnel


Je vous propose de raliser un deuxime projet qui permet de grer le personnel d'une entreprise, ce qui va nous permettre de voir comment transiter une entit au travers du rseau, tout en respectant le protocole SOAP / XML. Par contre, nous fairons trs peu de description, uniquement ce qui s'avrera ncessaire.

Constitution du projet dans son ensemble


Ce deuxime projet de gestion de personnel, comme le projet prcdent, englobe galement deux parties : D'une part un module EJB qui consiste en la ralisation du service web qui s'occupe de la gestion du personnel avec une persistance intgre sur le serveur distant. D'autre part, nous allons mettre en oeuvre une application cliente qui va utiliser ce service web et dont l'objectif est de grer le personnel : cration d'une nouvelle personne, modification, suppression, etc.

Module EJB - Cration du service web et de la persistance


Procdons par ordre et commenons par nous occuper du serveur. Il est ncessaire de prvoir un projet de type "Module EJB". Le nom du projet s'intitule tout simplement Personnels, et possde deux lments importants, d'une part, le bean session sans tat GestionPersonnel qui intgre le service web et qui gre la persistance, et l'entit Personne, qui se proccupe de rendre persistant chaque personnel de l'entreprise. Commenons justement par cette dernire :

entit.Personne.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQuery(name="toutLePersonnel", query="SELECT p FROM Personne p ORDER BY p.nom") public class Personne implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String nom; private String prenom; private int age; @ElementCollection(fetch=FetchType.EAGER) private List<String> telephones = new ArrayList<String>(); public long getId() { return id; } public void setId(long id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }

public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom.toUpperCase(); } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { StringBuilder chaine = new StringBuilder(prenom.toLowerCase()); chaine.setCharAt(0, Character.toUpperCase(chaine.charAt(0))); this.prenom = chaine.toString(); } public List<String> getTelephones() { return telephones; } public void setTelephones(List<String> telephones) { this.telephones = telephones; } } Au pralable, il est bien sr ncessaire de crer la base de donnes qui va intgrer toutes les tables issues de cette entit. Cette base de donnes s'intitule personnels. Par ailleurs, vous devez construire l'unit de persistance adquate. Tout cela, nous l'avons dj trait lors de l'tude de la gestion de la persistance, se ralise trs simplement au moyen de Netbeans. Vous pouvez ds lors appliquer cette entit. Remarquez au passage, que le nom et le prnom, lors de leurs mise en place par leurs mthodes set respectives, sont automatiquement mis en forme afin de prendre en compte la casse des caractres, tout en majuscule pour le nom, et la premire lettre en majuscule pour le prnom. Toutefois, le point important dans ce projet est de permettre l'envoi et la rception de cette entit au travers du protocole SOAP / XML. Il est donc galement ncessaire que l'objet relatif cette entit puisse tre automatiquement transforme en document XML et vice versa. Encore une fois, grce la technologie Java, et plus prcisment grce JAXB, il suffit d'une toute petite annotation @XmlRootElement pour que le mapping Objet / XML se ralise automatiquement dans les deux sens. Il n'y a vraiment pas grand chose crire. Mise part cet aspect particulier, le contenu mme de l'entit reste tout fait classique. Un objet Personne est chang entre le consommateur et le service web. Lorsque nous avons dcrit l'architecture d'un service web, nous avons vu que les donnes changes devaient tre des documents XML : nous avons donc besoin d'une mthode pour transformer un objet Java en XML et c'est l que JAXB entre en jeu avec ses annotations et son API. L'objet Personne doit simplement tre annot par @javax.xml.bind.annotation.XmlRootElement pour que JAXB le transforme en XML et rciproquement. Rappelez-vous que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Cette remarque est importante puisque les mthodes setNom() et setPrenom() ralisent pas mal de traitement avant de renseigner les attributs correspondants. Ainsi, lorsque le service web rcupre le document XML correspondant au personnel, le nom et le prnom de cette personne sont automatiquement formats comme nous le dsirons, juste au moment de la phase de dsrialisation.

session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.jws.WebService; import javax.persistence.*; @WebService @Stateless @LocalBean public class GestionPersonnel { @PersistenceContext private EntityManager bd; @Asynchronous public void nouveau(Personne personne) { bd.persist(personne); } private Personne rechercher(Personne personne) { return bd.find(Personne.class, personne.getId()); } @Asynchronous public void modifier(Personne personne) { bd.merge(personne); } @Asynchronous public void supprimer(Personne personne) { bd.remove(rechercher(personne)); } public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Passons maintenant au service web lui-mme. Il s'agit d'un bean session sans tat qui gre la persistance. Le service GestionPersonnel propose plusieurs mthodes pour grer l'ensemble du personnel, savoir nouveau(), modifier(), supprimer() et listePersonnels(). Remarquez que la mthode rechercher() est prive, c'est--dire quelle ne fait pas partie du service web. Elle est, par contre, bien utile pour la mthode supprimer(). Par ailleurs, beaucoup de mthodes de ce service web sont asynchrones, et sont donc annotes @Asynchronous, ce qui permet de librer tout de suite le client afin de grer ensuite en parallle la persistance qui peut prendre un peu de temps (le temps reste quand mme relativement court, c'est juste pour un cas d'cole).

Application cliente - Utilisation du service web de gestion du personnel


Le service est maintenant oprationnel, du moins si vous avez pens compiler votre module et le dployer sur le serveur Glassfish, tout ceci en l'excutant au travers de Netbeans. Nous pouvons maintenant travailler sur l'application cliente.

Je le rappelle, nous devons laborer un nouveau projet au travers d'une application Java classique. Avant d'crire le code relatif la mise en oeuvre de l'IHM, la toute premire chose s'occuper est de gnrer les artefacts ct client ( l'aide de wsimport) afin que nous disposions de toutes les classes ncessaires au dialogue avec le service web au travers du protocole SOAP/XML. Ces classes vont s'occuper de traduire ou de gnrer des documents XML. Ceci dit, encore une fois, il ne sera pas ncessaire de manipuler directement wsimport. Netbeans le fait votre place. Vous connaissez maintenant la procdure suivre. Il suffit de faire appel au menu WebServiceClient et de spcifier alors la localisation du document WSDL afin que les artefacts soient en adquation avec le service web utilis. Ds que vous validez votre choix, wsimport est automatiquement excut et gnre donc votre place l'ensemble des classes ncessaires la communication avec le web service au travers du protocole SOAP / XML.

Remarquez bien que la mthode rechercher() ne fait effectivement pas parti des mthodes du web service. Par ailleurs, la classe Personne est automatiquement gnre galement ct client par l'outil wsimport puisque la plupart des mthodes l'utilisent, soit en paramtre, soit en valeur de retour. N'oubliez pas que nous avons galement bien pris soin de placer l'annotation @XmlRootElement sur l'entit Personne, ct serveur. Si nous ouvrons le fichier de cette classe Personne ct client, et si nous supprimons tous les commentaires javadoc, voici ce que nous obtenons :

ws.Personne.java
package ws; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "personne", propOrder = { "age", "id", "nom", "prenom", "telephones" }) public class Personne { protected int age; protected long id; protected String nom; protected String prenom; @XmlElement(nillable = true) protected List<String> telephones; public int getAge() { return age; }

public void setAge(int value) { this.age = value; } public long getId() { return id; } public void setId(long value) { this.id = value; } public String getNom() { return nom; } public void setNom(String value) { this.nom = value; } public String getPrenom() { return prenom; } public void setPrenom(String value) { this.prenom = value; } public List<String> getTelephones() { if (telephones == null) { telephones = new ArrayList<String>(); } return this.telephones; } } Nous pouvons faire un certain nombre de remarques. Cette classe Personne, ct client est diffrente de l'originale, l'entit Personne sur le serveur. Seuls les attributs et les mthodes set et get sont ncessaires ce niveau. Je tiens souligner qu'il est trs importants que ces mthodes get et set soient prsentes dans l'entit, sinon, au moment de la gnration de code automatique avec l'utilitaire wsimport, nous ne les retrouverions pas ct client. Cette remarque est primordiale, ct client, les mthodes set sont utilises, nous l'avons vu, par le proxy. Ensuite, lorsque l'objet est compltement renseign, JAXB, en interne, utilise de son ct plutt les mthodes get. Les deux types de mthode sont donc indispensables si vous dsirez exprimer compltement l'objet transfrer. Rappelez-vous que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Soyez bien attentif galement ce qui est crit ct client. Les mthodes setNom() et setPrenom() n'ont plus du tout le traitement relatif la mise en majuscule. En ralit, ce n'est pas gnant et pas indispensable de ce ct l. Effectivement, pour bien dmontrer ce que je vous voque, je vais vous rappeler le cheminement de la description d'une personne au travers du rseau. Lorsque nous devons, par exemple, envoyer la description complte d'une personne du client vers le serveur. La toute premire chose est, bien entendu, la cration d'un objet Personne ct client. Nous devons ensuite spcifier la valeur des attributs l'aide des mthodes set, moins que nous ayons prvu un constructeur avec plusieurs paramtres qui nous permettrait de remplir dj les attributs concerns. Ensuite, de faon intrinsque, durant la phase de srialisation, le document XML est gnr l'aide cette fois-ci des mthodes get. Le document XML, avec son enveloppe SOAP, est alors envoy jusqu'au service web. Ce dernier, au moyen de JAXB, utilise maintenant les mthodes set de l'entit, donc finalement les mthodes setNom() et setPrenom() originales, pour dsrialiser et produire ainsi l'objet correspondant. C'est juste ce moment l que le nom et le prnom se retrouvent avec les lettres majuscules.

Il est possible de prvoir des mthodes supplmentaires, cot client, sur l'artefact Personne, afin que cela soit plus facile et plus intuitif utiliser par l'IHM. Je vous porpose donc de faire les rajouts ncessaires comme montr ci-dessous.

ws.Personne.java
package ws; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "personne", propOrder = { "age", "id", "nom", "prenom", "telephones" }) public class Personne { protected int age; protected long id; protected String nom; protected String prenom; @XmlElement(nillable = true) protected List<String> telephones; public int getAge() { return age; } public void setAge(int value) { this.age = value; } public long getId() { return id; } public void setId(long value) { this.id = value; } public String getNom() { return nom; } public void setNom(String value) { this.nom = value; } public String getPrenom() { return prenom; } public void setPrenom(String value) { this.prenom = value; } public List<String> getTelephones() { if (telephones == null) { telephones = new ArrayList<String>(); } return this.telephones; } public Personne() { } public Personne(String nom, String prenom, int age) { this.age = age; this.nom = nom;

this.prenom = prenom; } public void modifier(String nom, String prenom, int age) { this.age = age; this.nom = nom; this.prenom = prenom; } @Override public String toString() { return nom+' '+prenom; } } 1. Ainsi, l'artefact original est propos un constructeur o nous spcifions le nom, le prnom et l'ge du personnel. Attention, ds que vous crez un nouveau constructeur, vous tes oblig de prvoir le constructeur par dfaut. 2. J'en profite galement pour rajouter une mthode modifier() et pour redfinir la mthode toString() qui, dans ce dernier cas, me permet de visualiser automatiquement le nom suivi du prnom lorsque nous intgrons un personnel dans la liste droulante de l'IHM cliente. Maintenant tout est prt, nous pouvons donc crire notre codage Java correspondant toute la partie graphique afin de permettre la gestion complte du personnel de l'entreprise distance (mme sur Internet) et de mmoriser ainsi toutes les actions proposes sur la base de donnes du serveur et ceci, au travers du service web.

personnel.Client.java
package personnel; import java.awt.*; import java.awt.event.*; import javax.swing.*; import ws.*; public class Client extends JFrame { private JToolBar outils = new JToolBar(); private JTextField nom = new JTextField(); private JTextField prnom = new JTextField(); private JFormattedTextField ge = new JFormattedTextField(0); private JComboBox tlphones = new JComboBox(); private JPanel panneau = new JPanel(); private JComboBox liste = new JComboBox(); private static GestionPersonnel gestion; private Personne personne = new Personne(); private boolean effacer = true; public Client() { super("Personne"); add(outils, BorderLayout.NORTH); outils.add(new AbstractAction("Enregistrer") { public void actionPerformed(ActionEvent e) { personne = new Personne(nom.getText(), prnom.getText(), (Integer)ge.getValue()); for (int i=0; i<tlphones.getItemCount(); i++) personne.getTelephones().add((String) tlphones.getItemAt(i)); gestion.nouveau(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Ajout tlphone") { public void actionPerformed(ActionEvent e) { String tlphone = (String) tlphones.getSelectedItem(); tlphones.addItem(tlphone); } }); outils.add(new AbstractAction("Modifier") { public void actionPerformed(ActionEvent e) { personne.modifier(nom.getText(), prnom.getText(), (Integer)ge.getValue()); gestion.modifier(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { gestion.supprimer(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Effacer") { public void actionPerformed(ActionEvent e) { personne = new Personne(); rafrachir(); } }); panneau.setLayout(new GridLayout(4, 2)); panneau.add(new JLabel("Nom :")); panneau.add(nom); panneau.add(new JLabel("Prnom :")); panneau.add(prnom); panneau.add(new JLabel("ge :")); panneau.add(ge); panneau.add(new JLabel("Tlphones :")); panneau.add(tlphones); tlphones.setEditable(true); add(panneau); add(liste, BorderLayout.SOUTH); liste.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) { personne = (Personne) liste.getSelectedItem(); rafrachir(); } } }); listingPersonnes(); pack();

setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void listingPersonnes() { effacer = true; liste.removeAllItems(); effacer = false; for (Personne personne : gestion.listePersonnels()) liste.addItem(personne); } private void rafrachir() { nom.setText(personne.getNom()); prnom.setText(personne.getPrenom()); ge.setValue(personne.getAge()); tlphones.removeAllItems(); for (String tlphone : personne.getTelephones()) tlphones.addItem(tlphone); } public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } }

Diagramme de squences de Enregistrer nouveau personnel

Service web REST


La pile des services web (SOAP, WSDL, WS-*) dcrite au chapitre prcdent fournit la fois une interoprabilit pour l'injection des messages et le style RPC. Avec l'avnement du Web 2.0, un nouveau type de services web est devenu la mode : les services web REST. De nombreux acteurs essentiels du Web, comme Amazon, Google et Yahoo!, ont abandonn leurs services SOAP en faveur de services REST orients ressources. Lorsqu'il s'agit de choisir entre des services SOAP et REST, de nombreux facteurs entrent en ligne de compte. REST est intgr Java EE 6 via JAX-RS (Java API for RESTful Web Services), que nous utiliserons au cours de ce chapitre. REST (Representational State Transfert) est un type d'architecture reposant sur le fonctionnement mme du web, qu'il applique aux services web. Pour concevoir un service REST, il faut bien connatre tout simplement le protocole HTTP (Hypertext Transfert Protocole), le principe des URI (Uniform Resource Identifiers) et respecter quelques rgles. Il faut raisonner en terme de ressources.

Prsentation des services web REST


Dans l'architecture REST, toute information est une ressource et chacune d'elles est dsigne par une URI - gnralement un lien sur le Web. Les ressources sont manipules par un ensemble d'oprations simples et bien dfinies. L'architecture client-serveur de REST est conue pour utiliser un protocole de communication sans tat - le plus souvent HTTP. Ces principes encouragent la simplicit, la lgret et l'efficacit des applications.
Ressources Les ressources jouent un rle central dans les architectures REST. Une ressource est tout ce que peut dsigner ou manipuler un client, tout information pouvant tre rfrence dans un lien hypertexte. Elle peut tre stocke dans une base de donnes, un fichier, etc. Il faut viter autant que possible d'exposer des concepts abstraits sous forme de ressources et privilgier plutt les objets simples. URI Une ressource web est identifie par une URI, qui est un identifiant unique form d'un nom et d'une adresse indiquant o trouver la ressource. Il existe diffrents types d'URI : les adresses web, les UDI (Universal Document Identifiers), les URI (Uniform Resource Identifiers) et, enfin, les combinaisons d'URL (Uniform Resource Locator) et d'URN (Uniform Resource Name). Voici quelques exemples d'URI : * * * * * http://www.movies.fr/categories/aventure http://www.movies.fr/catalog/titles/movies/123456 http://www.weather.com/weather/2012?location=Aurillac,France http://www.flickr.com/explore/intersting/2012/01/01 http://www.flickr.com/explore/intersting/24hours

Les URI devraient tre aussi descriptives que possible et ne dsigner qu'une seule ressource, bien que des URI diffrentes qui identifient des ressources diffrentes puissent mener aux mmes donnes : un instant donn, la liste des photos intressantes publies sur Flickr le 01/01/2012 tait la mmea que la liste des photos donnes au cours des 24 dernires heures, bien que l'information envoye par les deux URI ne ft pas la mme.
Reprsentation Nous pouvons obtenir la reprsentation d'un objet sous forme de texte, de XML de PDF ou tout autre format. Un client traite toujours une ressource au travers de sa reprsentation ; la ressource elle-mme reste sur le serveur. La reprsentation contient toutes les informations utiles propos de l'tat d'une ressource : * http://www.apress.fr/book/catalog/java * http://www.apress.fr/book/catalog/java.csv * http://www.apress.fr/book/catalog/java.xml

La premire URI est la reprsentation par dfaut de la ressource, les reprsentations supplmentaires lui ajoutent simplement l'extension de leur format : .csv, .xml, .pdf, etc. L'autre solution consiste n'exposer qu'une seule URI pour toutes les reprsentation, comme la premire par exemple, et utiliser un mcanisme appel ngociation du contenu, que nous prsenterons un peu plus loin.

Protocole HTTP
La requte la plus simple du protocole HTTP est form de GET suivi d'une URL qui pointe sur des donnes (fichier statiques, traitement dynamique...). Elle est envoye par un navigateur quand nous saisissons directement une URL dans le champ d'adresse du navigateur. Le serveur HTTP rpond en renvoyant les donnes demandes.

En tapant l'URL d'un site, l'internaute envoie (via le navigateur) une requte au serveur. Une connexion s'tablit entre le client et le serveur sur le port 80 (port par dfaut d'un serveur Web). Le navigateur envoie une requte demandant l'affichage d'un document. La requte contient entre autres la mthode (GET, POST, etc.) qui prcise comment l'information est envoye. Le serveur rpond la requte en envoyant une rponse HTTP compose de plusieurs parties, dont : l'tat de la rponse, savoir une ligne de texte qui dcrit le rsultat du serveur (code 200 pour un accord, 400 pour une erreur due au client, 500 pour un erreur due au serveur) ; les donnes afficher.

Une fois la rponse reue par le client, la connexion est ferme. Pour afficher une nouvelle page du site, une nouvelle connexion doit tre tablie.

Requtes et rponses
Un client envoie une requte un serveur afin d'obtenir une rponse. Les messages utiliss pour ces changes sont forms d'une enveloppe et d'un corps galement appel document ou reprsentation. Voici, par exemple, un type de requte envoye un serveur : Cette requte contient plusieurs informations envoyes par le client :

La mthode HTTP GET ; Le chemin, ici la racine "/" ; Plusieurs autre en-ttes de requte. Vous remarquez que la requte n'a pas de corps (un GET n'a jamais de corps). En rponse, le serveur renvoie sa rponse et elle est forme des parties suivantes : Un code de rponse : ici le code est 200 OK. Plusieurs en-ttes de rponse, notamment Date, Server, Content-Type. Ici, le type de contenu est text/html, mais il pourrait s'agir de n'importe quel format comme du XML (application/xml) ou une image (image/jpeg), etc. Un corps ou reprsentation. Ici, il s'agit du contenu de la page web renvoye (qui n'est pas visible sur la figure propose ci-dessus). Pour en savoir plus sur le protocole HTTP et les en-ttes. .

Rappel sur les mthodes du HTTP


Le web est form de ressources bien identifies, relies ensemble et auxquelles accder au moyen de requtes HTTP simples. Les requtes principales de HTTP sont de type GET, POST, PUT et DELETE. Ces types sont appels verbes ou mthodes. HTTP dfinit quatre autres verbes plus rarement utiliss, HEAD, TRACE, OPTIONS et CONNECT.
GET GET est une mthode de lecture demandant une reprsentation d'une ressource. Elle doit tre implmente de sorte ne pas modifier l'tat de la ressource. En outre, GET doit tre idempotente, ce qui signifie qu'elle doit laisser la ressource dans le mme tat, quel que soit le nombre de fois o elle est appele. Ces deux caractristiques garantissent une plus grande stabilit : si un client n'obtient pas de rponse ( cause d'un problme rseau, par exemple), il peut renouveler sa requte et s'attendre la mme rponse que celle qu'il aurait obtenue initialement, sans corrompre l'tat de la ressource sur le serveur. POST A partir d'une reprsentation texte, XML, etc., POST cre une nouvelle ressource subordonne une ressource principale identifie par l'URI demande. Des exemples d'utilisation de POST sont l'ajout d'un message un fichier journal, d'un livre une liste d'ouvrages, etc. POST modifie donc l'tat de la ressource et n'est pas idempotente (envoyer deux fois la mme requte produit deux nouvelles ressources subordonnes). Si une ressource a t cre sur le serveur d'origine, le code de la rponse devrait tre 201 (Created). La plupart des navigateurs modernes ne produisent que des requtes GET et POST. PUT Une requte PUT est conue pour modifier l'tat de la ressource stocke une certaine URI. Si l'URI de la requte fait rfrence une ressource inexistante, celle-ci sera cre avec cette URI. PUT modifie donc l'tat de la ressource, mais elle est idempotente : mme si nous envoyons plusieurs fois la mme requte PUT, l'tat de la ressource finale restera inchang. DELETE Une requte DELETE supprime une ressource. La rponse DELETE peut tre un message d'tat dans le corps de la rponse ou aucun code du tout. DELETE est idempotente, mais elle modifie videmment l'tat de la ressource. HEAD HEAD ressemble GET sauf que le serveur ne renvoie pas de corps dans sa rponse. HEAD permet par exemple de vrifier la validit d'un client ou la taille d'une entit sans avoir besoin de la transfrer. TRACE TRACE retrace la requte reue. OPTION OPTION est une demande d'information sur les options de communication disponibles pour la chane requte/rponse identifie par l'URI. Cette mthode permet au client de connatre les options et/ou les exigences associes une ressource, ou les possibilits d'un serveur sans demander d'action sur une ressource et sans rcuprer aucune ressource. CONNECT CONNECT est utilis avec un proxy pouvant se transformer dynamiquement en tunnel (une technique grce laquelle le protocole HTTP sert d'enveloppe diffrents protocoles rseau).

Ngociation du contenu
La ngociation de contenu est dfinie comme "le fait de choisir la meilleure reprsentation pour une rponse donne lorsque plusieurs reprsentations sont disponibles". Les besoins, les souhaits et les capacits des clients varient : la meilleure reprsentation pour l'utilisateur d'un tlphone portable au Japon peut, en effet, ne pas tre la plus adapte un lecteur flux RSS en France.

La ngociation du contenu utilise entre autres les en-ttes HTTP : Accept, Accept-Charset, Accept-Encoding, Accept-Language et User-Agent. Pour obtenir, par exemple, la reprsentation CSV de la liste des livres sur Java publis par Apress, l'application cliente (l'agent utilisateur) demandera http://www.apress.com/books/catalog/java avec un en-tte Accept initialis text/csv. Vous pouvez aussi imaginer que, selon la valeur de l'en-tte Accept-Language, le contenu de ce document CSV pourrait tre traduit par le serveur dans la langue correspondante.

Types de contenu
HTTP utilise des types de supports Intenet (initialement appels types MIME) dans les en-ttes Content-Type et Accept afin de permettre un typage des donnes et une ngociation de contenu ouverts et extensibles. Les types de support Internet sont diviss en cinq catgories : text, image, audio, video et application. Ces types sont leur tour diviss en sous-types (text/plain, text/html, text/xhtml, etc.). Voici quelques-uns des plus utiliss :
text/html HTML est utilis par l'infrastructure d'information du World Wide Web depuis 1990 et sa spcification a t dcrite dans plusieurs documents informels. Le type de support text/html a t initialement dfini en 1995 par le groupe le groupe de traveil IETF HTML. Il permet d'envoyer et d'interprter les pages web classiques. text/plain Il s'agit du type de contenu par dfaut car il est utilis pour les messages textuels simples. imagegif, image/jpeg, image/png Le type de support image exige la prsence d'un dispositif d'affichage (un cran ou une imprimante graphique, par exemple) permettant de visualiser l'information. text/xml, application/xml Envoi et rception de document XML. application/json JSON est un format textuel lger pour l'change de donnes. Il est indpendant des langages de programmation.

Pour en savoir plus sur les types MIME. .

Code d'tat
Un code HTTP est associ chaque rponse. La spcification dfinit environ 60 codes d'tats ; l'lment Status-Code est un entier de trois chiffres qui dcrit le contexte d'une rponse et qui est intgr dans l'enveloppe de celle-ci. Le premier chiffre indique l'une des cinq classes de rponses possibles : 1xx : Information : La requte a t reue et le traitement se poursuit. 2xx : Succs : L'action a bien t reue, comprise et accepte. 3xx : Redirection : Une autre action est requise pour que la requte s'effectue. 4xx : Erreur du client : La requte contient des erreurs de syntaxe ou ne peut pas tre excute. 5xx : Erreur du serveur : Le serveur n'a pas russi excuter une requte pourtant apparemment valide.
Voici quelques codes d'tat que vous avez srement dj d rencontrer :

200 OK : La requte a russi. Le corps de l'entit, si elle en possde un, contient la reprsentation de la ressource. 301 Moved Permanently : La ressource demande a t affecte une autre URI permanente et toute rfrence future cette ressource devrait utiliser l'une des URI renvoyes. 404 Not Found : Le serveur n'a rien trouv qui corresponde l'URL demande. 500 Internal Server Error : Le serveur s'est trouv dans une situation inattendue qui l'a empch de rpondre la requte. Pour en savoir plus sur le protocole HTTP et les codes d'erreur. .

Mise en cache et requtes conditionnelles


La mise en cache est un lment crucial pour la plupart des systmes distribus. Elle a pour but d'amliorer les performances en vitant les requtes inutiles et en rduisant le volume de donnes des rponses. HTTP dispose de mcanisme permettant la mise en cache et la vrification de l'exactitude des donnes du cache. Si le client dcide de ne pas utiliser ce cache, il devra toujours demander les donnes, mme si elles n'ont pas t modifies depuis la dernire requte. La rponde une requte de type GET peut contenir un en-tte Last-Modified indiquant la date de dernire modification de la ressource. La prochaine fois que l'agent utilisateur demandera cette ressource, il passera cette date dans l'en-tte If-Modified-Since : le serveur web (ou le proxy) la comparera alors la date de dernire modification. Si celle envoye par l'agent utilisateur est gale ou plus rcente, le serveur renverra une rponse sans corps, avec un code d'tat 304 Not Modified. Sinon l'opration demande sera ralise ou transfre. Les dates peuvent tre difficiles manipuler et impliquent que les agents concerns soient, et restent, synchroniss : c'est le but de l'en-tte de rponse ETag, qui peut tre considr comme un hachage MD5 ou SHA1 de tous les octets d'une reprsentation - si un seul octet est modifi, la valeur d'ETag sera diffrente. La valeur ETag reue dans une rponse une requte GET peut, ensuite, tre affecte un en-tte If-Match d'une requte.

Spcification des services web REST


Contrairement SOAP et la pile WS-*, qui reposent sur les standards du W3C ; REST n'est pas un standard : c'est uniquement un style d'architecture respectant certains critres de conception. Les applications REST, cependant, dpendent fortement d'autres standards comme : HTTP, XML, JSON, JPEG, etc. Sa prise en compte par Java a t spcifie par JAX-RS, mais REST est comme un patron de conception : c'est une solution rutilisable d'un problme courant, qui peut tre implmente en diffrents langages.

JAX-RS 1.1
Pour crire des services web REST, il suffit d'un client et d'un serveur reconnaissant le protocole HTTP. N'importe quel navigateur et un conteneur de servlet HTTP pourraient donc faire l'affaire, au prix d'un peu de configuration XML et d'ajustement du code. Au final, ce code pourrait devenir peu lisible et difficile maintenir : c'est l que JAX-RS vole notre secours. Sa premire version, finalise en octobre 2008, dfinit un ensemble d'API mettant en avant une architecture REST. Au moyen d'annotations, il simplifie l'implmentation de ces services et amliore la productivit. La spcification ne couvre que la partie serveur du REST. Nouveauts de JAX-RS 1.1 : Il s'agit d'une version de maintenance axe sur l'intgration avec Java EE 6 et ses nouvelles fonctionnalits. Les nouveauts principales de JAX-RS 1.1 sont les suivantes : Le support de beans session sans tat comme ressources racine. Il est dsormais possible d'injecter des ressources externes (gestionnaire de persistance, sources de donnes, EJB, etc.) dans une ressource REST. Les annotations JAX-RS peuvent s'appliquer l'interface locale d'un bean ou directement un bean sans interface.

L'approche REST
Comme nous l'avons dj mentionn, REST est un ensemble de contraintes de conceptions gnrales reposant sur HTTP. Ce chapitre s'intressant aux services web et REST drivant du web, nous commencerons par une navigation relle passant en revue les principes du Web. Ce dernier est devenu une source essentielle d'informations et fait dsormais partie de nos outils quotidiens : vous le connaissez donc srement trs bien et cette familiarit vous aidera donc comprendre les concepts et les proprits de REST.

Du Web aux services web


Nous savons comment fonctionne le Web : pourquoi les services web devraient-ils se comporter diffremment ? Aprs tout, ils changent souvent uniquement des ressources bien identifies, lies d'autres au moyen de liens hypertextes. L'architecture du Web ayant prouv sa tenue en charge au cours du temps, pourquoi rinventer la roue ?
Pour crer, modifier et supprimer une ressource livre, pourquoi ne pas utiliser les verbes classiques de HTTP ? Par exemple :

Utiliser POST sur des donnes (au format XML, JSON ou texte) afin de crer une ressource livre avec l'URI http://www.site.com/livres/. Le livre cr, la rponse renvoie l'URI de la nouvelle ressource http://www.site.com/livres/123456. Utiliser GET pour lire la ressource (et les ventuels liens vers d'autres ressources partir du corps de l'entit) l'URI http://www.apress.com/books/123456. Utiliser PUT pour modifier la ressource l'URI http://www.site.com/livres/123456. Utiliser DELETE pour supprimer la ressource l'URI http://www.site.com/livres/123456. En se servant ainsi des verbes HTTP, nous pouvons donc effectuer toutes les actions CRUD (Create, Read, Update, Delete) sur une ressource l'image des bases de donnes.

Pratique de la navigation sur le web


Comment obtenir la liste des livres sur Java publis par ce site ? En faisant pointer son navigateur sur le site web : http://www.livre.com. Mme si cette page ne contiendra srement pas les informations exactes que nous recherchons, nous nous attendons ce qu'elle donne accs, par un moyen ou un autre, la liste des livres consacrs Java. La page d'accueil offre un moteur de recherche de tous les livres, mais galement un rpertoire de livres classs par technologies. Si nous cliquons sur le noeud Java, la magie de l'hypertexte opre et nous obtenons la liste complte des livres sur Java publis. Supposons que nous ayons sauvegard le lien dans notre gestionnaire de favoris et qu'au cours du parcours de la liste, un ouvrage particulier attire votre attention : le lien hypertexte sur le titre en question nous mnera la page contenant le rsum, la biographie des auteurs, etc. Nous vondrons comparer ce livre avec un autre ouvrage. Les pages des livres nous donnent accs une reprsentation plus concrte sous la forme de prvisualisations : nous pouvons alors ouvrir une prvisualisation, lire la table des matires et faire notre choix. Voici ce nous faisons quotidiennement avec nos navigateurs. REST applique les mmes principes vos services o les livres, les rsultats des recherches, une table des matires ou la couverture d'un livre peuvent tre dfinis comme des ressources.

Sans tat
La dernire fonctionnalit de REST est l'absence d'tat, ce qui signifie que toute requte HTTP est totalement indpendante puisque le serveur ne mmorisera jamais les requtes qui ont t effectues. Pour plus de clart, l'tat de la ressource et celui de l'application sont gnralement diffrencis : l'tat de la ressource doit se trouver sur le serveur et tre partag par tous, tandis que celui de l'application doit rester chez le client et tre sa seule proprit. Si nous revenons l'exemple des livres, l'tat de l'application est que le client a rcupr par exemple une reprsentation du livre dsir, mais le serveur ne mmorisera pas cette information. L'tat de la ressource, quant lui, est l'information sur l'ouvrage : le serveur doit videmment la mmoriser et le client peut le modifier. Si le panier virtuel est une ressource dont l'accs est rserv un seul client, l'application doit stocker l'identifiant de ce panier dans la session du client. L'absence d'tat possde de nombreux avantages, notamment une meilleure adaptation la charge : aucune information de session grer, pas besoin de router les requtes suivantes vers le mme serveur, gestion des erreurs, etc. Si vous devez mmoriser l'tat, le client devra faire un travail supplmentaire pour le stocker.

JAX-RS : Java API for RESTful Web Services


Vous pouvez vous demander quoi ressemblera le code qui s'appuie sur des concepts d'aussi bas niveau que le protocole HTTP. En ralit, vous n'avez pas besoin d'crire des requtes HTTP ni de crer manuellement des rponses car JAX-RS est une API trs lgante qui permet d'crire une ressource l'aide de quelques annotations seulement. Je vous propose toute de simple de prendre un exemple extrmement simple de service web REST qui utilise seulement la mthode GET. Ce service nous renvoie un texte non interprt qui nous souhaite la bienvenue.

Mise en oeuvre d'une application Web


La premire dmarche consiste utiliser une application Web au travers de laquelle nous allons annoter une simple classe Java (POJO) et renseigner le descripteur de dploiement web.xml qui va activer la servlet ServletContainer qui s'occupe d'implmenter la technologie REST (L'implmentation officielle de JAX-RS par Sun est Jersey). J'utilise Glassfish 3.1 et Netbeans pour ce dveloppement. Glassfish dans sa version Java EE 6 intgre en natif Jersey.

web.xml

<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet>

<servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

rest.Bienvenue.java
package rest; import javax.ws.rs.*; @Path("/bienvenue") public class Bienvenue { @GET @Produces("text/plain") public String getMessage() { return "Bienvenue... (premier exemple REST)"; } } Bienvenue tant une classe Java annote par @Path, la ressource sera hberge l'URI /bienvenue. La mthode getMessage() est elle-mme annote par @GET afin d'indiquer qu'elle traitera les requtes HTTP GET et qu'elle produit du texte (le contenu est identifi par le type MIME text/plain). Pour accder la ressource, il suffit d'un client HTTP, simple navigateur par exemple, pouvant envoyer une requte GET vers l'URL http://localhost:8080/AWRest/bienvenue.

Il est galement possible d'utiliser un client HTTP qui permet de tester les services web de type REST en nous montrant les diffrentes en-ttes. Vous pouvez installer par exemple le pluggin Poster du navigateur Firefox (ou autre).

Le code prcdent montre que le service REST n'implmente aucune interface et n'tend aucune classe : @Path est la seule annotation obligatoire pour transformer un POJO en service REST. JAX-RS utilisant la configuration par exception, un ensemble d'annotations permettent de modifier son comportement par dfaut. Les exigences que doit satisfaire une classe pour devenir REST sont les suivantes : Elle doit tre annot par @javax.ws.rs.Path. Pour ajouter les fonctionnalits des EJB au service REST, la classe doit tre annote @javax.ejb.Stateless. Elle doit tre publique et ne pas tre finale ni abstraite. Les classes ressources racine (celles ayant une annotation @Path) doivent avoir un constructeur par dfaut public (ou aucuns). Les classes ressources non racine n'exigent pas ce constructeur. La classe ne doit pas dfinir la mthode finalyze(). Par nature, JAX-RS repose sur HTTP et dispose d'un ensemble de classes et d'annotations clairement dfinies pour grer HTTP et les URI. Une ressource pouvant avoir plusieurs reprsentations, l'API permet de grer un certain nombre de types de contenu et utilise JAXB pour srialiser et dsrialiser les reprsentations XML et JSON en objets. JAX-RS tant indpendant du conteneur, les ressources peuvent tre videmment dployes dans Glassfish, mais galement dans un grand nombre d'autres conteneurs de servlets.

A l'aide d'un bean session


Les services REST peuvent galement tre des beans session sans tat, ce qui permet d'utiliser des transactions pour accder une couche de persistance l'aide des entits et des gestionnaires d'entits.

session.GestionPersonnel.java

package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.*; import javax.xml.bind.JAXBElement; @Path("/personnels") @Stateless @Produces({"application/xml", "application/json"}) @Consumes({"application/xml", "application/json"}) public class GestionPersonnel { @PersistenceContext private EntityManager bd; @Context private UriInfo uri; @GET public List<Personne> toutLePersonnel() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @POST public void nouveauPersonnel(JAXBElement<Personne> personne) { bd.persist(personne.getValue()); } }

entit.Personne.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @Entity @XmlRootElement @NamedQueries({ @NamedQuery(name="toutLePersonnel", query="SELECT p FROM Personne p ORDER BY p.nom, p.prnom"), @NamedQuery(name="recherchePersonnel", query="SELECT p FROM Personne p WHERE p.nom = :nom AND p.prnom = :prnom") }) public class Personne implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String nom; private String prnom; @Temporal(javax.persistence.TemporalType.DATE) private Date naissance; private String tlphone; public long getId() { return id; } public Date getNaissance() { return naissance; } public void setNaissance(Date naissance) { this.naissance = naissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom.toUpperCase(Locale.FRENCH); } public String getPrnom() { return prnom; } public void setPrnom(String prnom) { StringBuilder chaine = new StringBuilder(prnom.toLowerCase()); chaine.setCharAt(0, Character.toUpperCase(chaine.charAt(0))); this.prnom = chaine.toString(); } public String getTlphone() { return tlphone; } public void setTlphone(String tlphone) { this.tlphone = tlphone; } @Override public String toString() { return nom +" "+ prnom; } } Le code du bean session reprsente un service REST pouvant consommer et produire les reprsentations XML et JSON d'un personnel. La mthode toutLePersonnel() rcupre la liste de tout le personnel partir d'une base de donnes et renvoie sa reprsentation XML ou JSON (en utilisant la ngociation de contenu) ; elle est appele par une requte GET. La mthode nouveauPersonnel() prend une reprsentation XML ou JSON d'une personne et le stocke dans la base de donnes. Cette mthode est invoque par une requte POST et renvoie l'URI du nouveau livre.

Dfinition des URI


La valeur de l'annotation @Path est relative un chemin URI. Lorsqu'elle est utilise sur des classes, celles-ci sont considres comme des ressources racine, car elles fournissent la racine de l'arborescence des ressources et l'accs aux sous-ressources. Il est ainsi possible de rgler trs facilement le chemin de base de l'URI de telle sorte que cela corresponde la racine de l'application web du premier exemple de bienvenue. l'URL introduire au niveau du navigateur devient alors http://localhost:8080/AWRest/.

rest.Bienvenue.java
package rest; import javax.ws.rs.*; @Path("/") // Changement du chemin de l'URI pour que cela corresponde la racine de l'application Web public class Bienvenue { @GET @Produces("text/plain") public String getMessage() { return "Bienvenue... (premier exemple REST)"; } } Vous pouvez galement intgrer dans la syntaxe de l'URI des modles de chemins d'URI au moyen d'un nom de variable entour d'accolades : ces variables seront values l'excution. @Path("/personnels/{nom}") @Path peut galement s'appliquer aux mthodes des ressources racine, ce qui permet de regrouper les fonctionnalits plusieurs ressources, comme le montre le code suivant :

session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.*; import javax.xml.bind.JAXBElement; @Path("/personnels") @Stateless @Produces({"application/xml", "application/json"}) @Consumes({"application/xml", "application/json"}) public class GestionPersonnel { @PersistenceContext private EntityManager bd; @Context private UriInfo uri; @GET public List<Personne> toutLePersonnel() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Path("{idPersonnel}") public Personne unPersonnel(@PathParam("idPersonnel") long idPersonnel) { return bd.find(Personne.class, idPersonnel); } @GET @Path("requete") public Personne unPersonnel(@QueryParam("nom") String nom, @QueryParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } @GET @Path("/premiers/") public List<Personne> dixPremiers() { Query requte = bd.createNamedQuery("toutLePersonnel"); requte.setMaxResults(10); return requte.getResultList(); } @GET @Path("/suivants/{page}") public List<Personne> dixSuivants(@PathParam("page") int page) { Query requte = bd.createNamedQuery("toutLePersonnel"); requte.setFirstResult(page); requte.setMaxResults(10*page); return requte.getResultList(); } @POST public void nouveauPersonnel(JAXBElement<Personne> personne) { bd.persist(personne.getValue()); }

@DELETE @Path("{idPersonnel}") public void supprimePersonnel(@PathParam("idPersonnel") long idPersonnel) { bd.remove(unPersonnel(idPersonnel)); } @PUT @Path("{idPersonnel}") public void modifiePersonnel(@PathParam("idPersonnel") long idPersonnel) { bd.merge(unPersonnel(idPersonnel)); } } Si @Path est applique la fois sur la classe et une mthode, le chemin relatif de la ressource produite par cette mthode est la concatnation de la classe et de la mthode. Ainsi, pour obtenir un personnel par son identifiant, par exemple, le chemin sera /personnels/1234. Cette recherche sera effectue au travers de la mthode unPersonnel(). Si nous demandons la ressource racine /personnels, seule la mthode sans annotation @Path sera automatiquement slectionne : ici toutLePersonnel(). Si nous demandons /personnels/premiers, c'est la mthode dixPremiers() qui sera invoque. Si nous dsirons avoir les dix personnels de la troisime page, c'est la mthode dixSuivants() qui sera invoque en proposant l'URI /personnels/suivants/3. Si @Path("/personnels") n'annotait que la classe et aucune mthode, le chemin d'accs de toutes les mthodes serait le mme et il faudrait alors utiliser le verbe HTTP (GET, PUT) et la ngociation du contenu (texte, XML, etc.) pour les diffrencier.

Extraction des paramtres


Nous avons besoin d'extraire des informations sur les URI et les requtes lorsque nous les manipulons. Le code prcdent nous a dj montr comment extraire un paramtre du chemin l'aide de l'annotation @javax.ws.rs.PathParam. JAX-RS fournit un ensemble d'annotation supplmentaires pour extraire les diffrents paramtres qu'une requte peut envoyer (@QueryParam, @MatrixParam, @CookieParam et @FormParam). @PathParam Cette annotation permet d'extraire la valeur du paramtre d'une requte. Le code suivant permet d'extraire l'identifiant du personnel 1234 de l'URI : http://localhost:8080/RESTPersonnel/personnels/1234 : @Path("/personnels") public class GestionPersonnel { @GET @Path("{idPersonnel}") public Personne unPersonnel(@PathParam("idPersonnel") long idPersonnel) { return bd.find(Personne.class, idPersonnel); } ... } Dans l'annotation @Path associe une mthode, il est possible de proposer plusieurs variables qui deviendront les diffrents paramtres de la mthode. Il est ainsi possible de rprer un personnel partir de son nom et de son prnom grce l'URI suivante : http://localhost:8080/RESTPersonnel/personnels/nom-REMY-prenom-Emmanuel @Path("/personnels") public class GestionPersonnel { @GET @Path("nom-{nom}-prenom-{prenom}") public Personne unPersonnel(@PathParam("nom") String nom, @PathParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } Une particularit, c'est qu'il est possible de proposer une liste variable de paramtres dans votre URI. Chaque paramtre sera ensuite rcupr sparment. @Path("/bienvenue/{civilit}") public class Bienvenue { @GET @Path("{identit : .+}/age/{age}") @Produces("text/plain") public String rcuprer(@PathParam("civilit") String choix, @PathParam("identit") List<PathSegment> params, @PathParam("age") int age) { StringBuilder chane = new StringBuilder(choix+' '); for (PathSegment segment : params) chane.append(segment.getPath()+' '); chane.append(age+" ans"); return chane.toString(); } }

@QueryParam Cette annotation permet d'extraire la valeur d'un paramtre modle d'une URI. L aussi, le code suivant permet de retrouver un personnel partir de son nom et de son prnom au moyen de l'URI suivante : http://localhost:8080/RESTPersonnel/personnels?nom=REMY&prenom=Emmanuel : @Path("/personnels") public class GestionPersonnel { @GET

public Personne unPersonnel(@QueryParam("nom") String nom, @QueryParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } @MatrixParam Cette annotation agit comme @QueryParam, sauf qu'elle extrait la valeur d'un paramtre matrice d'une URI (le dlimiteur est ; au lieu de ?). Le code suivant permet galement, sous une autre forme, de retrouver un personnel partir de son nom et de son prnom avec l'URI suivante : http://localhost:8080/RESTPersonnel/personnels;nom=REMY;prenom=Emmanuel : @Path("/personnels") public class GestionPersonnel { @GET public Personne unPersonnel(@MatrixParam("nom") String nom, @MatrixParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } @CookieParam et @HeaderParam Deux autres mthodes sont lies aux dtails internes de HTTP, ce que nous ne voyons pas directement dans les URI : les cookies et les en-ttes. @CookieParam extrait la valeur d'un cookie, tandis que @HeaderParam permet d'obtenir la valeur d'un en-tte :

@Path("/") @Produces("text/plain") public class Bienvenue { @GET public String bienvenue() { return "Bienvenue... (premier exemple REST)"; } @GET @Path("paramtres") public String paramtres(@HeaderParam("Accept") String typeContenu, @HeaderParam("User-Agent") String agent) { return typeContenu+'\n'+agent; } } @FormParam Cette annotation prcise que la valeur d'un paramtre doit tre extraite d'un formulaire situ dans le corps de la requte : @Path("/personnels") public class GestionPersonnel { @GET @Path("formulaire") @Consumes("application/x-www-form-urlencoded") public Personne recherche(@FormParam("nom") String nom, @FormParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } @DefaultValue Nous pouvons ajouter @DefaultValue toutes ces annotations pour dfinir une valeur par dfaut pour le paramtre que nous attendons. Cette valeur sera utilise si les mtadonnes correspondantes sont absentes de la requte. Si le paramtre age ne se trouve pas dans la requte, par exemple, le code suivant utilisera la valeur 50 par dfaut : @Path("/personnels") public class GestionPersonnel { @GET public Personne unPersonnel(@DefaultValue("50") @QueryParam("age") int ge) { ... } }

Consommation et production des types de contenu


Avec REST, une ressource peut avoir plusieurs reprsentations : un personnel peut tre reprsent comme une page web, un document XML ou une image affichant sa photo d'identit. Les annotations @javax.ws.rs.Consumes et @javax.ws.rs.Produces peuvent s'appliquer une ressource qui propose plusieurs reprsentations : elle dfinit les types des

mdias changs entre le client et le serveur. L'utilisation de l'une de ces annotations sur une mthode redfinit celle qui s'appliquait sur la classe de la ressource pour un paramtre d'une mthode ou une valeur de retour. En leur absence, on suppose que la ressource supporte tous les types de mdia (*/*). L'expression du type de contenu (du type MIME) se fait au moyen d'une simple chaine de caractres, comme "text/plain" par exemple, ou au travers de la classe MediaType qui possde toutes les constantes prdfinies de chacun des types MIME connus, comme par exemple MediaType.TEXT_PLAIN. Dans le code qui suit, Bienvenue produit par dfaut une reprsentation en texte brut, sauf pour la premire mthode qui propose un rsultat sous forme de pas HTML.

rest.Bienvenue.java
package rest; import javax.ws.rs.*; @Path("/") @Produces("text/plain") public class Bienvenue { @GET @Produces("text/html") public String bienvenue() { return "<html><h2 align='center'>Bienvenue... (premier exemple REST)</h2></html>"; } @GET public String paramtres(@HeaderParam("Accept") String typeContenu, @HeaderParam("User-Agent") String agent) { return typeContenu+'\n'+agent; } } Si une ressource peut produire plusieurs types de mdia Internet, la mthode choisie correspondra au type qui convient le mieux l'en-tte Accept de la requte HTTP du client. Si, par exemple, cet en-tte est : Accept : text/plain et que l'URI est "/", c'est la mthode bienvenue() qui sera invoque. Le client aurait pu galement utiliser l'en-tte suivant : Accept : text/plain; q=0.8, text/html. Cet en-tte annonce que le client peut accepter les types text/plain et text/html mais qu'il prfre le dernier choix avec un facteur de qualit de 0.8 ("je prfre huit fois plus le text/html que le text/plain"). En incluant cet en-tte la mme requte "/", c'est la mthode paramtres() qui est cette fois-ci invoque :

Format JSON
JSON est un format lger pour l'change de donnes structures complexes. Il est l'image des documents XML en moins verbeux. Il est trs utile lorsque vous devez transfrer toutes les informations relative une entit par exemple. Voici ci-dessous un exemple de document JSON reprsentant une entit Personne qui peut disposer de plusieurs numros de tlphones : { "id" : 51,

"nom" : "REMY", "prnom" : "Emmanuel" "naissance" : 01/10/1959 "tlphones" : ["04-55-88-99-77", "06-89-89-87-77"] }

Fournisseur d'entits
Lorsque les entits sont reues dans des requtes ou envoyes dans des rponses, l'implmentation JAX-RS doit pouvoir convertir les reprsentations en Java et vice versa : c'est le rle des fournisseurs d'entits. JAXB, par exemple, traduit un objet en reprsentation et rciproquement. Il existe deux variantes de fournisseur d'entits : MessageBodyReader et MessageBodyWriter. Requtes Pour traduire le corps d'une requte en Java, une classe doit implmenter l'interface javax.ws.rs.MessageBodyReader et tre annote par @Provider. Par dfaut, l'implmentation est cense consommer tous les types de mdias (*/*), mais l'annotation @Consumes permet de restreindre les types supports. Rponses De la mme faon, un type Java peut tre traduit en corps de rponse. Une classe Java voulant effectuer ce traitement doit implmenter l'interface javax.ws.rs.MessageBodyWriter et tre annote par l'interface @Provider. L'annotation @Produces indique les types de mdias supports. Fournisseurs d'entits par dfaut L'implmentation de JAX-RS offre un ensemble de fournisseurs d'entits par dfaut convenant aux situations courantes. Type byte[] java.lang.String java.io.InputStream java.io.Reader java.io.File java.activation.DataSource javax.xml.transform.Source javax.xml.bind.JAXBElement MultivalueMap<String,String> javax.ws.rs.core.StreamingOutput Srialisation automatique JAX-RS peut automatiquement effectuer des oprations de srialisation et d-srialisation vers un type Java spcifique - */* : byte[] - text/* : String - text/xml, application/xml, application/*+xml : JAXBElement - application/x-www-form-urlencoded : MultivalueMap<String,String> Gestion du contenu : InputStream Requte et rponse avec un flux dentre : @POST @Path("flux") public void lireFlux(InputStream is) throws IOException { byte[] octets = lireOctets(is); String input = new String(octets); System.out.println(input); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1000]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } @Path("flux") @GET @Produces(MediaType.TEXT_XML) public InputStream envoyerDocumentXML() throws FileNotFoundException { return new FileInputStream("exemple.xml"); } Gestion du contenu : File Requte et rponse avec un fichier : @Path("fichier") @PUT public void lireFichier(File file) throws IOException { byte[] octets = lireOctets(new FileInputStream(file)); String input = new String(octets); System.out.println(input); } @Path("fichier") @GET @Produces(MediaType.TEXT_XML) public File envoyerFichier() { File file = new File("exemple.xml"); return file; } Gestion du contenu : byte[] Requte et rponse avec un tableau d'octets : Description Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Type XML (text/xml, application/xml et application/-*+xml) Types de mdia XML de JAXB (text/-xml, application/xml et application/*+xml) Contenu de formulaire (application/x-www-form-urlencoded). Tous les types de mdias (*/*), uniquement MessageBodyWriter.

@Path("/") public class Bienvenue { @GET @Produces("text/plain") public byte[] get() { return "Bienvenue tous".getBytes(); } @POST @Consumes("text/plain") public void post(byte[] octets) { System.out.println(new String(octets)); } } Gestion du contenu : String Requte et rponse avec une chane de caractres @Path("chaine") @PUT public void contenuChaine(String current) throws IOException { System.out.println(current); } @Path("chaine") @GET @Produces(MediaType.TEXT_XML) public String envoiChaine() { return "<?xml version=\"1.0\"?>" + "<details>Bienvenue tous" +"</details>"; } Types personnaliss : format XML Gestion d'un personnel de l'entreprise @GET @Produces(MediaType.APPLICATION_XML) public List<Personne> toutLePersonnel() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Produces(MediaType.APPLICATION_XML) // Le type MIME retourn par le service ce qui permet au client de connatre le format traiter @Path("{idPersonnel}") public Personne unPersonnel(@PathParam("idPersonnel") long idPersonnel) { return bd.find(Personne.class, idPersonnel); } @POST @Consumes(MediaType.APPLICATION_XML) // Utilisation dun objet JAXBElement pour envelopper le type Personne public void nouveauPersonnel(JAXBElement<Personne> personne) { bd.persist(personne.getValue()); } @DELETE @Path("{idPersonnel}") public void supprimePersonnel(@PathParam("idPersonnel") long idPersonnel) { bd.remove(unPersonnel(idPersonnel)); } @PUT @Consumes(MediaType.APPLICATION_XML) public void modifiePersonnel(Personne personne) { bd.merge(personne); }

Mthodes ou interface uniforme


Nous avons dcouvert comment le protocole HTTP grait ses requtes, ses rponses et ses mthodes d'actions (GET, POST, PUT, etc.). JAX-RS dfinit ces mthodes HTTP l'aide des annotations respectives : @GET, @POST, @PUT, @DELETE, @HEAD et @OPTIONS. Seules les mthodes publiques peuvent tre exposes comme des mthodes de ressources. Le code suivant montre une ressource personnels exposant les mthodes CRUD. @Path("/personnels") public class GestionPersonnel { @GET public List<Personne> toutLePersonnel() { ... } @POST @Consumes("application/xml") public Response nouveauPersonnel(InputStream is) { ... } @PUT @Path("{idPersonnel}") @Consumes(MediaType.APPLICATION_XML) public Response modifiePersonnel(Personne personne, InputStream is) { ... } @DELETE

@Path("{idPersonnel}") public void supprimePersonnel(@PathParam("idPersonnel") long idPersonnel) { ... } } Lorsqu'une mthode ressource est invoque, les paramtres annots par l'une des mthodes d'extraction vues prcdemment sont initialiss. La valeur d'un paramtre non annot (appel paramtre entit) est obtenue partir du corps de la requte et convertie par un des fournisseurs d'entits vus prcdemment. Les mthodes peuvent retrouner void, Response ou un autre type Java. Response indique qu'il faudra fournir d'autres mtadonnes. Lorsque nous crons un nouvel agent de l'entreprise, par exemple, il serait judicieux de renvoyer son URI personnelle.

La classe Response
Actuellement, tous les services dvelopps retournaient soit un type void soit un type Java dfini par le dveloppeur. JAX-RS facilite la construction de rponses en permettant de choisir un code de retour, de fournir des paramtres dans len-tte, de retourner une URI, etc. Les rponses complexes sont dfinies par la classe Response disposant de mthodes abstraites non utilisables directement : Retour de l'information globale, ce que nous nommons l'entit : getEntity(). Status de la rponse avec son code de succs ou d'erreur : getStatus(). Donnes de l'en-tte sous forme de couple cl/valeur : getMetaData(). Les informations de ces mthodes sont obtenues par des mthodes statiques retournant des ResponseBuilder.

Principales mthodes de la classe Response


ResponseBuilder created(URI locale) : Modifie la valeur de Location dans len-tte, utiliser pour une nouvelle ressource cre. ResponseBuilder notModified() : Statut Not Modified . ResponseBuilder ok() : Statut Ok . ResponseBuilder serverError() : Statut Server Error . ResponseBuilder status(Response.Status) : dfini un statut particulier dfini dans Response.Status.

Principales mthodes de la classe ReponseBuilder


Response build() : cre une instance. ResponseBuilder entity(Object value) : modifie le contenu du corps. ResponseBuilder header(String, Object) : modifie un paramtre de len-tte.

Exemple : Code de retour OK et ajout d'informations dans len-tte de la rponse


@Path("/bienvenue") public class Bienvenue { @GET @Produces("text/plain") public Response getBooks() { return Response.status(Response.Status.OK) .header("message1", "Bonjour") .header("message2", "Salut") .entity("Bienvenue... (premier exemple REST)") .build(); // Finalisation en appelant la mthode build() } }

Exemple : Code de retour avec erreur dans la rponse


@Path("/bienvenue") public class Bienvenue { @GET public Response getBooks() { return Response.serverError().build(); } }

// Finalisation en appelant la mthode build()

Informations contextuelles
Le fournisseur de ressources a besoin d'informations contextuelles pour traiter correctement une requte. L'annotation @javax.ws.rs.Context sert injecter les classes suivantes dans un attribut ou dans la paramtre d'une mthode : HttpHeaders (informations lies len-tte), UriInfo (informations lies aux URIs), Request (informations lies au traitement de la requte), SecurityContext (informations lies la scurit) et Providers. Ainsi, lannotation @Context permet dinjecter des objets lis au contexte de lapplication. Certains de ces objets permettent dobtenir les mmes informations que les prcdentes annotations lies aux paramtres. UriInfo Un objet de type UriInfo permet dextraire les informations brutes dune requte HTTP. Les principales mthodes sont les suivantes : - String getPath() : Chemin relatif de la requte. - MultivaluedMap<String, String> getPathParameters() : Valeurs des paramtres de la requte contenues dans Template Parameters. - MultivaluedMap<String, String> getQueryParameters() : Valeurs des paramtres de la requte. - URI getBaseUri() : Chemin de lapplication. - URI getAbsolutePath() : Chemin absolu (base + chemins). - URI getRequestUri() : Chemin absolu incluant les paramtres. @Path("/bienvenue") public class Bienvenue { @GET @Path("{param}") public void getInformationUriInfo(@Context UriInfo uriInfo, @PathParam("param") String param, @QueryParam("nom") String nom) { System.out.println("getPath() : " + uriInfo.getPath()); System.out.println("getAbsolutePath() : " + uriInfo.getAbsolutePath()); System.out.println("getBaseUri() : " + uriInfo.getBaseUri()); System.out.println("getRequestUri() : " + uriInfo.getRequestUri()); System.out.println("paramtre : "+ param); System.out.println("nom : "+ nom); System.out.println("getPathParameters() : "+uriInfo.getPathParameters()); System.out.println("getQueryParameters() : "+uriInfo.getQueryParameters()); } } Voici les rsultats obtenus sur le serveur d'application l'issue de cette requte http://localhost:8080/AWRest/bienvenue/test?nom=REMY Infos: getPath() : bienvenue/test Infos: getAbsolutePath() : http://localhost:8080/AWRest/bienvenue/test Infos: getBaseUri() : http://localhost:8080/AWRest/ Infos: getRequestUri() : http://localhost:8080/AWRest/bienvenue/test?nom=REMY Infos: paramtre : test Infos: nom : REMY Infos: getPathParameters() : {param=[test]} Infos: getQueryParameters() : {nom=[REMY]} Plutt que d'associer une information d'URI propose chacune des mthodes de la classe en spcifiant systmatiquement un paramtre supplmentaire, il peut tre judicieux de factoriser cette information en tant qu'attribut de la classe du web service REST. Ainsi, nous pourrons ventuellement exploiter ces informations pour chacune des mthodes de la classe. Voici le changement effectu de l'exemple prcdent : @Path("/bienvenue") public class Bienvenue { @Context UriInfo uriInfo; @GET @Path("{param}") public void getInformationUriInfo(@PathParam("param") String param, @QueryParam("nom") String nom) { System.out.println("getPath() : " + uriInfo.getPath()); System.out.println("getAbsolutePath() : " + uriInfo.getAbsolutePath()); System.out.println("getBaseUri() : " + uriInfo.getBaseUri()); System.out.println("getRequestUri() : " + uriInfo.getRequestUri()); System.out.println("paramtre : "+ param); System.out.println("nom : "+ nom); System.out.println("getPathParameters() : "+uriInfo.getPathParameters()); System.out.println("getQueryParameters() : "+uriInfo.getQueryParameters()); } } Les en-ttes : HttpHeaders Comme nous l'avons vu prcdemment, les informations transportes entre le client et le serveur sont formes non pas uniquement du corps d'une entit, mais galement d'en-ttes (Date, Content-type, etc.). Les en-ttes HTTP font partie de l'interface uniforme et les services web REST les utilisent. La classe javax.ws.rs.HttpHeaders peut tre injecte dans un attribut ou dans un paramtre de mthode au moyen de l'annotation @Context afin de permettre d'accder aux valeurs des en-ttes sans tenir compte de leur casse. Un objet de type HttpHeader permet dextraire les informations contenues dans len-tte dune requte. Les principales mthodes sont les suivantes : - Map<String, Cookie> getCookies() : les cookies de la requte. - Locale getLanguage() : la langue de la requte. - MultivaluedMap<String, String> getRequestHeaders() : valeurs des paramtres de len-tte de la requte. - MediaType getMediaType() : le type MIME de la requte. A noter que ces mthodes permettent dobtenir le mme rsultat que les annotations @HeaderParam et @CookieParam. @Path("/bienvenue") public class Bienvenue { @GET @Produces("text/plain") public Response get(@Context HttpHeaders enTtes) { StringBuilder chane = new StringBuilder(enTtes.getAcceptableLanguages().toString()); chane.append('\n'); chane.append(enTtes.getRequestHeader("accept-language")); return Response.ok ().entity(chane.toString()).build(); } }

L aussi, nous pouvons prendre un attribut de type HttpHeaders plutt de la prvoir pour chacune des mthodes de la classe du web service REST : @Path("/bienvenue") public class Bienvenue { @Context HttpHeaders enTtes; @GET @Produces("text/plain") public Response get() { StringBuilder chane = new StringBuilder(enTtes.getAcceptableLanguages().toString()); chane.append('\n'); chane.append(enTtes.getRequestHeader("accept-language")); return Response.ok ().entity(chane.toString()).build(); } }

Construction d'URI
Les liens hypertextes sont un lment central des applications REST. Afin d'voluer travers les tats de l'application, les services web REST doivent grer les transitions et la construction des URIs. Pour ce faire, JAX-RS fournit une classe javax.ws.rs.core.UriBuilder destine remplacer java.net.URI et faciliter la construction d'URI. UriBuilder dispose d'un ensemble de mthodes permettant de construire de nouvelles URIs ou d'en fabriquer partir d'URIs existantes. La classe utilitaire UriBuilder permet de construire des URIs complexes. Il est possible de construire des URIs avec UriBuilder via UriInfo (au moyen de l'annotation @Context comme prcdemment) o toutes URIs seront relatives au chemin de la requte. Voici les mthodes pour obtenir un UriBuilder : UriBuilder getBaseUriBuilder() : relatif au chemin de lapplication. UriBuilder getAbsolutePathBuilder() : relatif au chemin absolu (base+chemins). UriBuilder getRequestUriBuilder() : relatif au chemin absolu incluant les paramtres. Le principe dutilisation de la classe utilitaire UriBuilder est identique ResponseBuilder. Les principales mthodes sont les suivantes : URI build(Object... values) : construit une URI partir dune liste de valeurs pour les Template Parameters. UriBuilder queryParam(String name, Object...values) : ajoute des paramtres de requte. UriBuilder path(String path) : ajout un chemin de requte. UriBuilder fromUri(String uri) : nouvelle instance partir dune URI. UriBuilder host(String host) : modifie lURI de lhte. @Path("/bienvenue") public class Bienvenue { @Context UriInfo info; @GET public Response get() { UriBuilder fabrique = info.getAbsolutePathBuilder(); URI uri = fabrique.path("{nom}/{prenom}").queryParam("age", "{age}").build("REMY", "Emmanuel", 59); return Response.created (uri).build(); } }

Gestion des exceptions


Les codes que nous avons prsents jusqu' maintenant s'excutaient dans un monde parfait, o tout se passe bien et o il n'y a pas besoin de traiter les exceptions.

Malheureusement, ce monde n'existe pas et, tt ou tard, une ressource nous exploser en plein visage parce que les donnes reues ne sont pas valides ou que des parties du rseau ne sont pas fiable. Comme le montre le code suivant, nous pouvons lever tout instant une exception WebApplicationException ou l'une de ses sous-classes dans un fournisseur de ressources. Cette exception sera capture par l'implmentation de JAX-RS et convertie en rponse HTTP. L'erreur par dfaut est un code 500 avec un message vide, mais la classe javax.ws.rs.WebApplicationException offre diffrents constructeurs permettant de choisir un code d'tat spcifique (dfini dans l'numration javax.ws.rs.core.Response.Status) ou une entit. Les exceptions non controles et les erreurs qui n'entrent pas dans les deux cas prcdents seront relances comme toute exception Java non contrle.

rest.Bienvenue.java
@Path("/bienvenue") public class Bienvenue { @GET @Path("age/{age}") @Produces("text/html") public String getAge(@PathParam("age") int age) { if (age<0) throw new WebApplicationException(Response.status(400).entity("Vous devez donner une valeur positive...").build()); return "Age = "+age+ " ans"; } }

Cycle de vie
Lorsqu'une requte arrive, la ressource cible est rsolue et une nouvelle instance de la classe ressource racine correspondante est cre. Le cycle de vie d'une classe ressource racine dure donc le temps d'une requte, ce qui implique que la classe ressource n'a pas s'occuper des problmes de concurrence et qu'elle peut donc utiliser les variables d'instance en toute scurit. S'ils sont employs dans un conteneur Java EE (servlet ou EJB), les classes ressources et les fournisseurs JAX-RS peuvent galement utiliser les annotations de gestion de cycle de vie et de la scurit : @PostConstruct, @Predestroy, @RunAs, @RolesAllowed, @PermitAll, @DenyAll et @DeclareRoles. Le cycle de vie d'une ressource peut ainsi se servir de @PostConstruct et de @PreDestroy pour ajouter de la logique mtier respectivement aprs la cration d'une ressource et avant sa suppression.

Mise en oeuvre d'un projet de gestion du personnel


Je vous propose de mettre en oeuvre un projet qui va nous permettre de valider toutes ces nouvelles comptences. Ce projet sera plac dans une application Web qui sera accessible de faon classique au travers d'un simple navigateur. Il sera aussi possible de grer le personnel au travers d'un service web REST l'aide de mthodes adaptes pas mal de situations diffrentes. Dans ce dernier cas, nous pourrons grer le personnel l'aide d'une application cliente Android par exemple. L'application web utilise les comptences de composants de haut niveau proposs par PrimeFaces. Il sera donc ncessaire de dployer, en mme temps que le projet principal, les archives primefaces-3.2.jar (pour l'ensemble des composants JSF) ainsi que sunny.jar (pour le thme).

Je vous donne l'ensembles des codes ncessaire cette application web suivi des diffrents tests pour valuer le fonctionnement correct du service web REST.

web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" ... <context-param> <param-name>primefaces.THEME</param-name> <param-value>sunny</param-value>

</context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app> Nous remarquons ici la prsence de deux servlets, la premire relative JSF qui sert de contrleur pour les requtes classiques de l'application web, la deuxime s'occupant plus particulirement des fonctionnalits du service web REST.

entit.Personne.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQuery(name="toutLePersonnel", query="SELECT p FROM Personne p ORDER BY p.nom, p.prnom") public class Personne implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String nom; private String prnom; @Temporal(javax.persistence.TemporalType.DATE) private Date naissance; private String tlphone; public Personne() {} public Personne(String nom, String prnom, Date naissance, String tlphone) { setNom(nom); setPrnom(prnom); this.naissance = naissance; this.tlphone = tlphone; } public long getId() { return id; } public Date getNaissance() { return naissance; } public void setNaissance(Date naissance) { this.naissance = naissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom.toUpperCase(Locale.FRANCE); } public String getTlphone() { return tlphone; } public void setTlphone(String tlphone) { this.tlphone = tlphone; } public String getPrnom() { return prnom; } public void setPrnom(String prnom) { StringBuilder chane = new StringBuilder(prnom.toLowerCase(Locale.FRANCE)); chane.setCharAt(0, Character.toUpperCase(chane.charAt(0))); this.prnom = chane.toString(); } @Override public String toString() { return nom + " " + prnom; } } Il s'agit ici de l'entit Personne. Remarquez au passage que cette entit possde l'annotation indispensable @XmlRootElement si vous souhaitez soumettre cette entit au format JSON.

service.GestionPersonnel.java
package service; import entit.Personne; import java.util.*; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.Response; @Path("/") @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd;

@GET @Produces("application/json") @Path("tous") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Produces("application/json") @Path("json/{id}") public Personne rechercheJSON(@PathParam("id") long id) { return bd.find(Personne.class, id); } @GET @Path("{id}") public Response rechercheDtaille(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); return Response.ok ().header("nom", personne.getNom()) .header("prenom", personne.getPrnom()) .header("date", personne.getNaissance().getTime()) .header("telephone", personne.getTlphone()).build(); } @POST public Response nouveau(@HeaderParam("nom") String nom, @HeaderParam("prenom") String prnom, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = new Personne(nom, prnom, new Date(date), tlphone); bd.persist(personne); return Response.ok ().header("id", personne.getId()).build(); } @POST @Path("{nom}/{prnom}/{tlphone}") public Response nouveau(@PathParam("nom") String nom, @PathParam("prnom") String prnom, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = new Personne(nom, prnom, new GregorianCalendar(anne, mois-1, jour).getTime(), tlphone); bd.persist(personne); return Response.ok ().header("id", personne.getId()).build(); } @POST @Path("json") @Consumes("application/json") public void nouveau(Personne personne) { bd.persist(personne); } @PUT @Path("enTte/{id}") public void modifier(@PathParam("id") long id, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new Date(date)); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/{tlphone}") public void modifier(@PathParam("id") long id, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/telephone({tlphone})") public void modifier(@PathParam("id") long id, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/jour={jour}-mois={mois}-annee={annee}") public void modifier(@PathParam("id") long id, @PathParam("annee") int anne, @PathParam("mois") int mois, @PathParam("jour") int jour) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); bd.merge(personne); } @PUT @Path("json") @Consumes("application/json") public void modifier(Personne personne) { bd.merge(personne);

} @DELETE @Path("{id}") public void supprimer(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); bd.remove(personne); } @DELETE @Path("json") @Consumes("application/json") public void supprimer(Personne personne) { Personne recherche = bd.find(Personne.class, personne.getId()); bd.remove(recherche); } } Le bean session GestionPersonnel sert la fois pour l'application web classique avec des mthodes qui seront utilises en local par le bean reprsentant le modle de la structure JSF, mais rend aussi service l'extrieur directement, la fois par les mmes mthodes, mais galement par des mthodes plus spcifiques pour le service web REST.

bean.Personnel.java
package bean; import entit.Personne; import java.util.List; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.faces.application.FacesMessage; import javax.faces.bean.*; import javax.faces.context.FacesContext; import service.GestionPersonnel; @ManagedBean @ViewScoped public class Personnel { private Personne personne; @EJB private GestionPersonnel gestion; private List<Personne> tous; private int indice; @PostConstruct private void init() { tous = gestion.listePersonnels(); if (tous.isEmpty()) personne = new Personne(); else { indice = tous.size()-1; personne = tous.get(indice); } } public Personne getPersonne() { return personne; } public void setPersonne(Personne personne) { this.personne = personne; } public List<Personne> getTous() { return tous; } public boolean isNouveau() { return personne.getId() == 0; } public void nouveau() { personne = new Personne(); message("Nouvel agent", "Saisissez l'ensemble des coordonnes"); } public void enregistrer() { if (isNouveau()) gestion.nouveau(personne); else gestion.modifier(personne); init(); message("Enregistrement", personne+" est bien enregistre"); } public void supprimer() { message("Suppression", personne+" ne fait plus partie du personnel"); gestion.supprimer(personne); init(); } public void prcdent() { if (indice>0) indice--; personne = tous.get(indice); } public void suivant() { if (indice<tous.size()-1) indice++; personne = tous.get(indice); } private void message(String titre, String dtail) { FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(titre, dtail)); } } Ce code correspond au bean manag Personnel qui utilise les comptences du bean session GestionPersonnel vu prcdemment. Il sert de modle dans la structure JSF et il est en troite relation avec la vue reprsente par la page web index.xhtml.

index.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"

xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Gestion du personnel</title> </h:head> <h:body> <h:form> <p:growl autoUpdate="true" showDetail="true" /> <p:panel header="Gestion du personnel"> <p:accordionPanel activeIndex="-1"> <p:tab title="Liste du personnel"> <p:outputPanel autoUpdate="true"> <p:dataTable var="personne" value="#{personnel.tous}" paginator="true" rows="7"> <p:column> <f:facet name="header"><h:outputText value="Nom" /></f:facet> <h:outputText value="#{personne.nom}" /> </p:column> <p:column> <f:facet name="header"><h:outputText value="Prnom" /></f:facet> <h:outputText value="#{personne.prnom}" /> </p:column> <p:column> <f:facet name="header"><h:outputText value="Date de naissance" /></f:facet> <p:calendar locale="fr" value="#{personne.naissance}" pattern="EEEE dd MMMM yyyy" disabled="true"/> </p:column> <p:column> <f:facet name="header"><h:outputText value="Tlphones" /></f:facet> <h:outputText var="tlphone" value="#{personne.tlphone}" /> </p:column> </p:dataTable> </p:outputPanel> </p:tab> <p:tab title="Edition de chaque agent"> <p:outputPanel autoUpdate="true"> <p:panelGrid columns="2"> <h:outputText value="Nom :" /> <p:inputText value="#{personnel.personne.nom}" disabled="#{not personnel.nouveau}" /> <h:outputText value="Prnom :" /> <p:inputText value="#{personnel.personne.prnom}" disabled="#{not personnel.nouveau}" /> <h:outputText value="Date de naissance :" /> <p:calendar locale="fr" value="#{personnel.personne.naissance}" beforeShowDay="true" pattern="dd MMMM yyyy" /> <h:outputText value="Tlphone :" /> <p:inputMask value="#{personnel.personne.tlphone}" mask="99-99-99-99-99" /> <f:facet name="footer"> <p:commandButton value="Nouveau" action="#{personnel.nouveau()}" icon="ui-icon-document"/> <p:commandButton value="Enregistrer" action="#{personnel.enregistrer()}" icon="ui-icon-disk"/> <p:commandButton value="Supprimer" action="#{personnel.supprimer()}" disabled="#{personnel.nouveau}" icon="ui-icon-trash" /> <p:commandButton value="Prcedent" action="#{personnel.prcdent()}" /> <p:commandButton value="Suivant" action="#{personnel.suivant()}" /> </f:facet> </p:panelGrid> </p:outputPanel> </p:tab> </p:accordionPanel> </p:panel> </h:form> </h:body> </html> Il s'agit ici de la vue prsente au navigateur lorsque nous lanons l'application web, la page web index.xhtml.

Rcupration d'informations - Utilisation de la mthode GET du service web REST


@Path("/") @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd;

@GET @Produces("application/json") @Path("tous") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Produces("application/json") @Path("json/{id}") public Personne rechercheJSON(@PathParam("id") long id) { return bd.find(Personne.class, id); } @GET @Path("{id}") public Response rechercheDtaille(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); return Response.ok ().header("nom", personne.getNom()) .header("prenom", personne.getPrnom()) .header("date", personne.getNaissance().getTime()) .header("telephone", personne.getTlphone()).build(); } ... } Ce service propose trois mthodes avec l'annotation @GET. Le premire permet de restituer l'ensemble du personnel au format JSON. Voici ce que nous obtenons respectivement au travers d'un navigateur et du pluggin Poster. Dans ce dernier cas, les accents sont pris en compte. Remarquez bien au passage l'URI soumettre pour que ce service soit oprationnel.

La deuxime mthode permet de retourner un personnel galement au format JSON partir de son identifiant :

Enfin, la dernire mthode retourne galement un seul personnel, mais les informations retournes se situent dans l'en-tte de la rponse :

Enregistrement de nouvelles informations - Utilisation de la mthode POST du service web REST


@Path("/") @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd; @POST public Response nouveau(@HeaderParam("nom") String nom, @HeaderParam("prenom") String prnom, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = new Personne(nom, prnom, new Date(date), tlphone); bd.persist(personne); return Response.ok ().header("id", personne.getId()).build(); } @POST @Path("{nom}/{prnom}/{tlphone}") public Response nouveau(@PathParam("nom") String nom, @PathParam("prnom") String prnom, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = new Personne(nom, prnom, new GregorianCalendar(anne, mois-1, jour).getTime(), tlphone); bd.persist(personne); return Response.ok ().header("id", personne.getId()).build(); } @POST @Path("json") @Consumes("application/json") public void nouveau(Personne personne) { bd.persist(personne); } ... } Ce service propose deux mthodes avec l'annotation @POST. Le premire permet de crer un nouveau personnel partir de l'en-tte de la requte :

La deuxime mthode permet galement de crer un nouveau personnel directement au travers de l'URI. L'avantage d'utiliser des paramtres de type @QueryParam, c'est que lorsque nous laborons notre URI, l'ordre des paramtres n'a pas d'importance du moment que nous les voquons tous.

Enfin, la troisime mthode, qui sert galement pour le bean gr, permet au travers du service web REST de gnrer un nouveau personnel directement partir d'un document JSON. Comme l'entit possde l'annotation @XmlRootElement, le mapping entre le document JSON et la classe Personne se fait automatiquement.

Modification des informations dj enregistres - Utilisation de la mthode PUT du service web REST
@Path("/") @Stateless public class GestionPersonnel {

@PersistenceContext private EntityManager bd; @PUT @Path("enTte/{id}") public void modifier(@PathParam("id") long id, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new Date(date)); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/{tlphone}") public void modifier(@PathParam("id") long id, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/telephone({tlphone})") public void modifier(@PathParam("id") long id, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/jour={jour}-mois={mois}-annee={annee}") public void modifier(@PathParam("id") long id, @PathParam("annee") int anne, @PathParam("mois") int mois, @PathParam("jour") int jour) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); bd.merge(personne); } @PUT @Path("json") @Consumes("application/json") public void modifier(Personne personne) { bd.merge(personne); } ... } Ce service propose cette fois-ci quatre mthodes avec l'annotation @PUT. Le premire permet de modifier le numro de tlphone ainsi que la date de naissance en plaant les nouvelles valeurs dans l'en-tte de la requte, en spcifiant l'identifiant du personnel modifier directement dans l'URI.

La deuxime mthode modifie galement le numro de tlphone et la date de naissance du personnel identifi, mais cette fois-ci tout doit tre spcifi directement dans l'URI et au travers de paramtres de requte :

La troisime mthode ne modifie que le numro de tlphone du personnel identifi :

La quatrime mthode ne modifie quant elle que la date de naissance :

La dernire version permet de modifier la totalit du personnel l'aide d'un document JSON.

Suppression d'informations - Utilisation de la mthode DELETE du service web REST


@Path("/") @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd; ... @DELETE @Path("{id}") public void supprimer(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); bd.remove(personne); } @DELETE @Path("json") @Consumes("application/json") public void supprimer(Personne personne) { Personne recherche = bd.find(Personne.class, personne.getId()); bd.remove(recherche); } } Il n'existe ici deux mthodes de suppression, reconnaissables par l'annotation @DELETE. Dans le premier cas, il suffit de prciser l'identifiant du personnel supprimer :

Dans le deuxime cas, nous envoyons la totalit des informations du personnel au travers d'un document JSON.

Client du service web REST


Aprs avoir structurer notre service web REST, je vous propose de fabriquer un client sous Android qui gre l'ensemble du personnel, avec tous les modes d'dition possibles.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.rest" android:versionCode="1" android:versionName="1.0"> <application android:label="Client REST Personnel" > <activity android:name="ListePersonnel" android:label="Liste du Personnel"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="ChoixServeur" android:label="Choix du serveur" android:theme="@android:style/Theme.Dialog" /> <activity android:name="Personnel" android:label="Edition du Personnel" android:theme="@android:style/Theme.Dialog"/> </application> <uses-permission android:name="android.permission.INTERNET" /> </manifest>

res/drawable/fond.xml
<?xml version="1.0" encoding="UTF-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:startColor="#FF0000" android:endColor="#FFFF00" android:type="radial" android:gradientRadius="300" /> </shape>

res/layout/adresse.xml
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="2dp"> <Button android:id="@+id/ok" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" android:onClick="ok"/> <EditText android:layout_toLeftOf="@id/ok" android:id="@+id/adresse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Adresse IP" /> </RelativeLayout>

fr.btsiris.rest.ChoixServeur.java
package fr.btsiris.rest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.*; import android.widget.EditText; public class ChoixServeur extends Activity { private EditText adresse; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.adresse); adresse = (EditText) findViewById(R.id .adresse); }

public void ok(View vue) { Intent intent = new Intent(); intent.putExtra("adresse", adresse.getText().toString()); setResult(RESULT_OK, intent); finish(); } }

res/layout/liste.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/fond" android:padding="3dp"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Nouvelle personne" android:onClick="edition" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>

fr.btsiris.rest.Personne.java
package fr.btsiris.rest;

import java.io.Serializable; public class Personne implements Serializable { private long id; private String nom; private String prenom; private long naissance; private String telephone; public long getId() { return id; } public void setId(long id) { this.id = id; } public long getNaissance() { return naissance; } public void setNaissance(long naissance) { this.naissance = naissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { this.prenom = prenom; } @Override public String toString() { return nom + " " + prenom; } }

fr.btsiris.rest.ListePersonnel.java
package fr.btsiris.rest; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.*; import java.io.IOException; import java.util.*; import org.apache.http.HttpResponse; import org.apache.http.client.*; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.*; public class ListePersonnel extends ListActivity { private ArrayList<Personne> personnes = new ArrayList<Personne>(); private String adresse; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.liste); startActivityForResult(new Intent(this, ChoixServeur.class), 1); } protected void onActivityResult(int requestCode, int resultCode, Intent intention) { if (resultCode == RESULT_OK) { adresse = intention.getStringExtra("adresse"); try { miseAJour(); } catch (Exception ex) { Toast.makeText(this, "Rponse incorrecte", Toast.LENGTH_SHORT).show(); } } } @Override protected void onResume() { super.onResume(); if (adresse!=null) try { miseAJour();

} catch (Exception ex) { } } @Override protected void onStop() { super.onStop(); finish(); } public void miseAJour() throws IOException, JSONException { HttpClient client = new DefaultHttpClient(); HttpGet requete = new HttpGet("http://"+adresse+":8080/Personnel/rest/tous/"); HttpResponse reponse = client.execute(requete); if (reponse.getStatusLine().getStatusCode() == 200) { Scanner lecture = new Scanner(reponse.getEntity().getContent()); StringBuilder contenu = new StringBuilder(); while (lecture.hasNextLine()) contenu.append(lecture.nextLine()+'\n'); JSONArray tableauJSON = new JSONArray(contenu.toString()); StringBuilder message = new StringBuilder(); personnes.clear(); for (int i=0; i<tableauJSON.length(); i++) { JSONObject json = tableauJSON.getJSONObject(i); Personne personne = new Personne(); personne.setId(json.getLong("id")); personne.setNom(json.getString("nom")); personne.setPrenom(json.getString("prnom")); personne.setNaissance(json.getLong("naissance")); personne.setTelephone(json.getString("tlphone")); personnes.add(personne); } setListAdapter(new ArrayAdapter<Personne>(this, android.R.layout.simple_list_item_1 , personnes)); } else Toast.makeText(this, "Problme de communication", Toast.LENGTH_SHORT).show(); } public void edition(View vue) { Intent intention = new Intent(this, Personnel.class); intention.putExtra("id", 0); intention.putExtra("adresse", adresse); startActivity(intention); } @Override protected void onListItemClick(ListView liste, View vue, int position, long id) { Personne personne = personnes.get(position); Intent intention = new Intent(this, Personnel.class); intention.putExtra("id", personne.getId()); intention.putExtra("adresse", adresse); intention.putExtra("nom", personne.getNom()); intention.putExtra("prenom", personne.getPrenom()); intention.putExtra("naissance", personne.getNaissance()); intention.putExtra("telephone", personne.getTelephone()); startActivity(intention); } }

res/layout/personnel.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/fond" android:padding="3px"> <EditText android:id="@+id/nom" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Nom" /> <EditText android:id="@+id/prenom" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Prnom" /> <EditText android:id="@+id/naissance" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Date naissance" android:onClick="changeDate"/> <EditText android:id="@+id/telephone" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="n tlphone"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Nouveau" android:onClick="nouveau"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enregistrer" android:onClick="enregistrer"/> <Button android:id="@+id/supprimer" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="Supprimer" android:onClick="supprimer"/> </LinearLayout> </LinearLayout>

fr.btsiris.rest.Personnel.java
package fr.btsiris.rest; import android.app.*; import android.content.Intent; import android.os.Bundle; import android.text.format.DateFormat; import android.view.*; import android.widget.*; import java.io.IOException; import java.util.*; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.*; import org.apache.http.impl.client.DefaultHttpClient; public class Personnel extends Activity { private EditText nom, prenom, naissance, telephone; private Button supprimer; private long id; private String adresse; private Calendar calendrier = Calendar.getInstance(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.personnel); nom = (EditText) findViewById(R.id .nom); prenom = (EditText) findViewById(R.id .prenom); telephone = (EditText) findViewById(R.id .telephone); naissance = (EditText) findViewById(R.id .naissance); supprimer = (Button) findViewById(R.id .supprimer); } @Override protected void onStart() { super.onStart(); Intent intention = getIntent(); Bundle donnees = intention.getExtras(); id = donnees.getLong("id"); adresse = donnees.getString("adresse"); if (id==0) toutEffacer(); else { nom.setEnabled(false); prenom.setEnabled(false); supprimer.setEnabled(true); nom.setText(donnees.getString("nom")); prenom.setText(donnees.getString("prenom")); long date = donnees.getLong("naissance"); calendrier.setTimeInMillis(date); naissance.setText(DateFormat.format("EEEE dd MMMM yyyy", date)); telephone.setText(donnees.getString("telephone")); } } private void toutEffacer() { id = 0; nom.setEnabled(true); prenom.setEnabled(true); supprimer.setEnabled(false); nom.setText(""); prenom.setText(""); naissance.setText(""); telephone.setText(""); calendrier = Calendar.getInstance(); } private DatePickerDialog.OnDateSetListener evt = new DatePickerDialog.OnDateSetListener() { public void onDateSet(DatePicker dialog, int annee, int mois, int jour) { calendrier.set(annee, mois, jour); naissance.setText(DateFormat.format("EEEE dd MMMM yyyy", calendrier)); } }; public void changeDate(View vue) { new DatePickerDialog(this, evt, calendrier.get(Calendar.YEAR), calendrier.get(Calendar.MONTH), calendrier.get(Calendar.DAY_OF_MONTH)).show(); } public void nouveau(View vue) { toutEffacer(); } public void enregistrer(View vue) throws IOException { if (id==0) nouveauPersonnel(); else modifierPersonne(); } public void supprimer(View vue) throws IOException { HttpClient client = new DefaultHttpClient(); HttpDelete requete = new HttpDelete("http://"+adresse+":8080/Personnel/rest/"+id); client.execute(requete); Toast.makeText(this, "Personnel "+id+" supprim", Toast.LENGTH_SHORT).show(); finish();

} private void nouveauPersonnel() throws IOException { HttpClient client = new DefaultHttpClient(); HttpPost requete = new HttpPost("http://"+adresse+":8080/Personnel/rest/"); requete.addHeader("nom", nom.getText().toString()); requete.addHeader("prenom", prenom.getText().toString()); requete.addHeader("date", ""+calendrier.getTimeInMillis()); requete.addHeader("telephone", telephone.getText().toString()); client.execute(requete); Toast.makeText(this, "Nouveau personnel enregistr", Toast.LENGTH_SHORT).show(); finish(); } private void modifierPersonne() throws IOException { HttpClient client = new DefaultHttpClient(); HttpPut requete = new HttpPut("http://"+adresse+":8080/Personnel/rest/enTte/"+id); requete.setHeader("date", ""+calendrier.getTimeInMillis()); requete.setHeader("telephone", telephone.getText().toString()); client.execute(requete); Toast.makeText(this, "Personnel modifi", Toast.LENGTH_SHORT).show(); } }

Mise en oeuvre d'un projet d'archivage de photos


Le deuxime projet sera plus simple puisque, au travers d'une simple petite application web, nous allons archiver et visualiser un ensemble de photos. L aussi, l'application web utilise les comptences de composants de haut niveau proposs par PrimeFaces. Il sera donc de nouveau ncessaire de dployer, en mme temps que le projet principal, les archives primefaces-3.2.jar (pour l'ensemble des composants JSF) ainsi que sunny.jar (pour le thme).

Je vous donne l'ensemble des codes ncessaires cette application web suivi des diffrents tests pour valuer le bon fonctionnement du service web REST.

web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <context-param> <param-name>primefaces.THEME</param-name> <param-value>sunny</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping>

<welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app> Nous remarquons ici la prsence de deux servlets, la premire relative JSF qui sert de contrleur pour les requtes classiques de l'application web, la deuxime s'occupant plus particulirement des fonctionnalits du service web REST.

service.Archivage.java
package service; import java.io.*; import javax.annotation.PostConstruct; import javax.faces.bean.*; import javax.ws.rs.*; @Path("/") @ManagedBean public class Archivage { private final String rpertoire = "ArchivagePhotos/"; @PostConstruct private void init() { File rep = new File(rpertoire); if (!rep.exists()) rep.mkdir(); } @GET @Path("liste") @Produces("application/json") public String[] getPhotos() { return new File(rpertoire).list(); } @GET @Path("{nomFichier}") @Produces("image/jpeg") public InputStream restituer(@PathParam("nomFichier") String nom) throws FileNotFoundException { return new FileInputStream(rpertoire+nom); } @POST @Path("{nomFichier}") @Consumes("image/jpeg") public void stocker(@PathParam("nomFichier") String nom, InputStream flux) throws IOException { byte[] octets = lireOctets(flux); FileOutputStream fichier = new FileOutputStream(rpertoire+nom); fichier.write(octets); fichier.close(); } @DELETE @Path("{nomFichier}") public void supprimer(@PathParam("nomFichier") String nom) { new File(rpertoire+nom).delete(); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } } La grande particularit ici, c'est que notre service web REST est galement un bean manag qui va tre galement utile pour la vue de la structure JSF. Par contre, il ne s'agit pas d'un bean session puisque la persistance, au travers d'entit, n'est pas gre. Un rpertoire de stockage est prvu directement dans le serveur d'application utilis. Cela se produit la premire fois que le service entre en action au moyen de la mthode de rappel init(), grce l'annotation @PostConstruct. Nous retrouvons ensuite tous les mthodes classiques du protocole HTTP au moyen des annotation spcifiques @GET, @POST et @DELETE. Seule la mthode @PUT n'est pas utilise ici puisqu'elle ne correspond aucune fonctionnalit adapte ce genre de problme. Une autre grande particularit, que nous dcouvrirons tout l'heure, c'est que la mthode restituer() va galement tre utilise par la vue afin de proposer le flux de chaque image la galerie prsente.

index.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Archivage de photos</title> </h:head> <h:body> <h:form> <p:panel header="Archivage de photos"> <p:galleria value="#{archivage.photos}" var="photo"> <p:graphicImage value="rest/#{photo}" /> </p:galleria> </p:panel> </h:form> </h:body> </html>

La vue de index.xhtml est extrmement simple. La particularit, comme je viens de l'voquer, est d'utiliser le bean grer comme service web REST en ce qui concerne la visualisation de chaque photo, au travers de la mthode restituer() qui renvoie le flux de la photo concerne.

Enregistrement d'une nouvelle photo - Utilisation de la mthode POST du service web REST
@Path("/") @Produces("image/jpeg") @Consumes("image/jpeg") @ManagedBean public class Archivage { private final String rpertoire = "ArchivagePhotos/"; ... @POST @Path("{nomFichier}") public void stocker(@PathParam("nomFichier") String nom, InputStream flux) throws IOException { byte[] octets = lireOctets(flux); FileOutputStream fichier = new FileOutputStream(rpertoire+nom); fichier.write(octets); fichier.close(); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } } Voici la procdure suivre pour intgrer une nouvelle photo dans votre service. Il suffit de prciser le nom de fichier dans l'URI et slectionner le fichier image qui est le contenu de la requte :

Client du service web REST


Aprs avoir structurer notre service web REST, je vous propose de fabriquer un client sous Android qui permet d'envoyer des photos prises avec l'APN intgr.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.photos" android:versionCode="1" android:versionName="1.0"> <application android:label="Stockage photos" > <activity android:name="ArchivagePhotos" android:label="Stocker vos photos" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="ChoixServeur" android:label="Choix du serveur" android:theme="@android:style/Theme.Dialog" /> </application> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest>

res/layout/adresse.xml
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="2dp"> <Button android:id="@+id/ok" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" android:onClick="ok"/> <EditText android:layout_toLeftOf="@id/ok" android:id="@+id/adresse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Adresse IP" /> </RelativeLayout>

fr.btsiris.rest.ChoixServeur.java
package fr.btsiris.rest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.*; import android.widget.EditText; public class ChoixServeur extends Activity { private EditText adresse; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.adresse); adresse = (EditText) findViewById(R.id .adresse); } public void ok(View vue) { Intent intent = new Intent(); intent.putExtra("adresse", adresse.getText().toString()); setResult(RESULT_OK, intent); finish(); } }

res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#008800" android:padding="3dp"> <EditText android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Description de la photo" android:layout_alignParentBottom="true"/> <LinearLayout android:id="@+id/boutons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@id/description"> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Photographier" android:onClick="photographier" /> <Button android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="Envoyer" android:onClick="envoyer" /> </LinearLayout> <ImageView android:id="@+id/image" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:layout_above="@id/boutons" /> </RelativeLayout>

fr.btsiris.rest.ArchivagePhotos.java
package fr.btsiris.photos; import android.app.Activity; import android.content.Intent; import android.graphics.*; import android.net.Uri; import android.os.*; import android.provider.MediaStore; import android.view.View; import android.widget.*; import java.io.*; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.FileEntity; import org.apache.http.impl.client.DefaultHttpClient; public class ArchivagePhotos extends Activity { private EditText description; private ImageView image; private Uri fichierUri; private String adresse; private final int RECHERCHE_ADRESSE = 1; private final int PHOTOGRAPHIER = 2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); description = (EditText) findViewById(R.id .description); image = (ImageView) findViewById(R.id .image); startActivityForResult(new Intent(this, ChoixServeur.class), RECHERCHE_ADRESSE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intention) { switch (requestCode) { case RECHERCHE_ADRESSE : adresse = intention.getStringExtra("adresse"); break; case PHOTOGRAPHIER : File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); Bitmap photo = BitmapFactory.decodeFile(fichier.getPath()); image.setImageBitmap(photo); break; } } public void photographier(View vue) { Intent intention = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); fichierUri = Uri.fromFile(fichier); intention.putExtra(MediaStore.EXTRA_OUTPUT, fichierUri); startActivityForResult(intention, PHOTOGRAPHIER); } public void envoyer(View vue) throws IOException { HttpClient client = new DefaultHttpClient(); HttpPost requete = new HttpPost("http://"+adresse+":8080/Photos/rest/"+description.getText().toString()+".jpg"); requete.setEntity(new FileEntity(new File(Environment.getExternalStorageDirectory(), "photo.jpg"), "image/jpeg")); client.execute(requete); } }

Projet complet d'archivage de photos


Pour conclure sur les web services de type REST, je vous propose de reprendre l'ide d'archivage de photos en intgrant un certain nombre de fonctionnalits, comme la golocalisation, l'dition des photos, l'affichage sous forme de gallerie d'images, etc...

Je vous donne l'ensembles des codes ncessaires ce service. Pour que le projet fonctionne correstement, vous avez un certain nombre d'archives intgrer : primefaces-3.2.jar : Archive des composants de haut niveau pour JSF. afterdark-1.0.5.jar : Thme utilis dans ce projet en relation avec PrimeFaces. commons-fileupload-1.2.2.jar : Ncessaire pour permettre l'archivage des photos, en relation avec PrimeFaces. commons-io-2.1.jar : Egalement ncessaire pour permettre l'archivage des photos, en relation avec PrimeFaces. metadata-extractor-2.5.0-RC3.jar : Extraction des donnes EXIF dans les photos.

web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <context-param> <param-name>primefaces.THEME</param-name> <param-value>afterdark</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <filter> <filter-name>PrimeFaces FileUpload Filter</filter-name> <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class> </filter> <filter-mapping> <filter-name>PrimeFaces FileUpload Filter</filter-name> <servlet-name>Faces Servlet</servlet-name> </filter-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app>

index.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Archivage de photos</title> </h:head> <script src="http://maps.google.com/maps/api/js?sensor=true" type="text/javascript" /> <h:body style="background-color: darkseagreen"> <h:form> <p:growl autoUpdate="true" showDetail="true" /> <p:panel header="Archivage de photos"> <p:outputPanel autoUpdate="true"> <p:selectOneMenu value="#{archivage.titre}"> <f:selectItems value="#{archivage.listeDesLots}" /> <p:ajax listener="#{archivage.changeLot()}"/> </p:selectOneMenu>

</p:outputPanel> <p:tabView> <p:tab title="Gallerie des photos"> <p:outputPanel autoUpdate="true"> <p:galleria value="#{archivage.lot.photos}" var="photo" effect="slide" filmstripPosition="right" showOverlays="true" frameWidth="90" frameHeight="60" > <p:graphicImage value="rest/#{photo.id}" width="600" height="400" styleClass="ui-corner-all" /> <p:galleriaOverlay title="#{photo}"> <h:outputText value="#{photo.jour}" rendered="#{photo.exif}"> <f:convertDateTime dateStyle="full" /> </h:outputText> <br /> <h:outputText value="(Lat. = #{photo.latitude}, Lon.= #{photo.longitude})" rendered="#{photo.exif}" /> <h:outputText value="Aucunes donnes EXIF" rendered="#{not photo.exif}" /> </p:galleriaOverlay> </p:galleria> </p:outputPanel> </p:tab> <p:tab title="Google Map"> <p:outputPanel autoUpdate="#{archivage.miseAJour}"> <p:gmap zoom="15" type="HYBRID" center="#{archivage.centre}" style="width:600px;height:400px" model="#{archivage.cartographie}"> <p:ajax event="overlaySelect" listener="#{archivage.choixMarqueur}" /> <p:gmapInfoWindow> <p:graphicImage value="rest/#{archivage.marqueur.data}" width="200" /><br /> <h:outputText value="#{archivage.marqueur.title}" /> </p:gmapInfoWindow> </p:gmap> </p:outputPanel> </p:tab> <p:tab title="Edition des photos"> <p:outputPanel autoUpdate="true"> Titre : <p:inputText value="#{archivage.lot.titre}" /> <p:commandButton icon="ui-icon-disk" value="Changer le titre" action="#{archivage.changeTitre()}" /> <p:commandButton icon="ui-icon-trash" value="Supprimer totalement ce lot" action="#{archivage.supprimeLot()}" /> <p:dataTable value="#{archivage.lot.photos}" var="photo" paginator="true" rows="4"> <p:column> <f:facet name="header">Photo</f:facet> <a target="_blank" href="rest/#{photo.id}"> <p:graphicImage value="rest/#{photo.id}" width="200" styleClass="ui-corner-all" id="image"/> </a> <p:tooltip for="image" value="Cliquez pour avoir un Zoom sur cette photo" showEffect="slide" /> </p:column> <p:column> <f:facet name="header">Traitements annexes</f:facet> <p:inputText value="#{photo.description}" /> <p:commandButton icon="ui-icon-disk" value="Changer la description" action="#{archivage.changeDescription(photo)}" /> <p:commandButton icon="ui-icon-trash" value="Supprimer la photo" action="#{archivage.supprimePhoto(photo)}" /> </p:column> </p:dataTable> </p:outputPanel> </p:tab> <p:tab title="Ajouter une nouvelle photo"> Description obligatoire de la photo : <p:inputText value="#{archivage.description}"> <p:ajax event="keyup" /> </p:inputText> <p:fileUpload fileUploadListener="#{archivage.stockerPhoto}" mode="advanced" label="Choisissez votre photo" uploadLabel="Archiver" cancelLabel="Annuler" /> </p:tab> </p:tabView> <f:facet name="footer"> Titre : <p:inputText value="#{archivage.nouveauTitre}" /> <p:commandButton icon="ui-icon-document" value="Cration d'un nouveau lot de photos" action="#{archivage.nouveauLot()}" /> </f:facet> </p:panel> </h:form> </h:body> </html>

service.Archivage.java
package service; import entit.*; import java.io.*; import java.util.*; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.faces.application.FacesMessage; import javax.faces.bean.*; import javax.faces.context.FacesContext; import org.primefaces.event.FileUploadEvent; import org.primefaces.event.map.OverlaySelectEvent; import org.primefaces.model.*; import org.primefaces.model.map.*; @ManagedBean @SessionScoped public class Archivage { @EJB private ServiceREST service; private LotDePhotos lot; private List<LotDePhotos> listeDesLots; private String titre; private String nouveauTitre; private MapModel cartographie = new DefaultMapModel(); private String centre; private Marker marqueur; private boolean miseAJour; private String description; @PostConstruct private void init() { File rep = new File(service.getRpertoire()); if (!rep.exists()) rep.mkdir(); listeDesLots = service.getListeDesLots(); if (!listeDesLots.isEmpty()) lot = listeDesLots.get(0);

marqueurs(); } public boolean isMiseAJour() { return miseAJour; } public String getTitre() { return titre; } public void setTitre(String titre) { this.titre = titre; } public String getNouveauTitre() { return nouveauTitre; } public void setNouveauTitre(String nouveauTitre) { this.nouveauTitre = nouveauTitre; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public List<LotDePhotos> getListeDesLots() { return listeDesLots; } public LotDePhotos getLot() { return lot; } public Photo getPhoto(String id) { return service.recherchePhoto(id); } public String getCentre() { return centre; } public MapModel getCartographie() { return cartographie; } public void changeLot() { lot = service.rechercheLot(titre); marqueurs(); miseAJour = true; } public void nouveauLot() { service.crationLotDePhotos(nouveauTitre); listeDesLots = service.getListeDesLots(); } public void changeTitre() { service.modifierTitreLotDePhoto(lot); listeDesLots = service.getListeDesLots(); } public void supprimeLot() { service.supprimeLot(lot.getId()); init(); } public void changeDescription(Photo photo) { service.modifierDescriptionPhoto(photo); } public void supprimePhoto(Photo photo) { service.supprimePhoto(lot, photo); } public void stockerPhoto(FileUploadEvent evt) { try { UploadedFile fichier = evt.getFile(); service.stocker(lot.getId(), description, fichier.getContents()); message(description, "Votre photo vient d'tre stocke dans le service"); lot = service.rechercheLot(titre); } catch (Exception ex) { message("ATTENTION", "Impossible de stocker votre photo dans le service"); } } public void choixMarqueur(OverlaySelectEvent event) { marqueur = (Marker) event.getOverlay(); miseAJour = false; } public Marker getMarqueur() { return marqueur; } private void marqueurs() { for (Photo photo : lot.getPhotos()) { if (photo.isExif()) { LatLng coordonnes = new LatLng(photo.getLatitude(), photo.getLongitude()); marqueur = new Marker(coordonnes, photo.getDescription(), photo.getId()); cartographie.addOverlay(marqueur); centre = photo.getLatitude()+", "+photo.getLongitude(); } } } private void message(String titre, String dtail) { FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(titre, dtail)); } }

service.ServiceREST.java
package service; import com.drew.imaging.ImageMetadataReader; import com.drew.metadata.*; import com.drew.metadata.exif.GpsDirectory; import entit.*; import java.io.*; import java.util.*; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.Response; @Path("/") @Stateless @LocalBean public class ServiceREST { private final String rpertoire = "ArchivagePhotos/"; @PersistenceContext private EntityManager bd; @POST @Path("{titre}") public Response crationLotDePhotos(@PathParam("titre") String titre) { LotDePhotos lot = new LotDePhotos(titre); bd.persist(lot);

Query requte = bd.createNamedQuery("recherche"); requte.setParameter("titre", titre); lot = (LotDePhotos) requte.getSingleResult(); return Response.ok ().header("id", lot.getId()).build(); } @POST @Path("{id}/description={description}") @Consumes("image/jpeg") public void stocker(@PathParam("id") long id, @PathParam("description") String description, InputStream flux) throws Exception { byte[] octets = lireOctets(flux); stocker(id, description, octets); } @PUT @Path("{id}/titre={titre}") public void changeTitre(@PathParam("id") long id, @PathParam("titre") String titre) { LotDePhotos lot = bd.find(LotDePhotos.class, id); lot.setTitre(titre); bd.merge(lot); } @GET @Path("{nomFichier}") @Produces("image/jpeg") public InputStream restituer(@PathParam("nomFichier") String nom) throws FileNotFoundException { return new FileInputStream(rpertoire+nom); } @GET @Produces("application/json") public List<LotDePhotos> getListeDesLots() { return bd.createNamedQuery("toutLesLots").getResultList(); } @DELETE @Path("{id}") public void supprimeLot(@PathParam("id") long id) { LotDePhotos lot = bd.find(LotDePhotos.class, id); for (Photo photo : lot.getPhotos()) new File(rpertoire+photo.getId()).delete(); bd.remove(lot); } public LotDePhotos rechercheLot(String titre) { Query requte = bd.createNamedQuery("recherche"); requte.setParameter("titre", titre); return (LotDePhotos) requte.getSingleResult(); } public Photo recherchePhoto(String id) { return (Photo) bd.find(Photo.class, id); } public void modifierTitreLotDePhoto(LotDePhotos lot) { bd.merge(lot); } public void modifierDescriptionPhoto(Photo photo) { bd.merge(photo); } public void supprimePhoto(LotDePhotos lot, Photo photo) { lot.getPhotos().remove(photo); bd.merge(lot); bd.remove(recherchePhoto(photo.getId())); new File(rpertoire+photo.getId()).delete(); } public String getRpertoire() { return rpertoire; } public void stocker(long id, String description, byte[] octets) throws Exception { Photo photo = new Photo(description); FileOutputStream fichier = new FileOutputStream(rpertoire+photo.getId()); fichier.write(octets); fichier.close(); exif(rpertoire, photo); LotDePhotos lot = bd.find(LotDePhotos.class, id); lot.ajoutPhoto(photo); bd.merge(lot); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } private void exif(String rpertoire, Photo photo) throws Exception { Metadata metadata = ImageMetadataReader.readMetadata (new File(rpertoire+photo.getId())); Directory exif = metadata.getDirectory(GpsDirectory.class); boolean EXIF; try { exif.getDescription(GpsDirectory.TAG_GPS_LATITUDE); EXIF = true; } catch (Exception ex) { EXIF = false; } photo.setExif(EXIF); if (photo.isExif()) { photo.setLatitude(toDouble(exif.getDescription(GpsDirectory.TAG_GPS_LATITUDE))); photo.setLongitude(toDouble(exif.getDescription(GpsDirectory.TAG_GPS_LONGITUDE))); photo.setJour(date(exif.getDescription(GpsDirectory.TAG_GPS_DATE_STAMP))); } } private double toDouble(String localisation) { String[] degrs = localisation.split("");

double valeur = Integer.parseInt(degrs[0]); String[] minutes = degrs[1].split("'"); valeur += Integer.parseInt(minutes[0]) / (double)60; String[] secondes = minutes[1].split("\""); valeur += Double.parseDouble(secondes[0]) / 3600; return valeur; } private Date date(String date) { String[] sparation = date.split(":"); int anne = Integer.parseInt(sparation[0]); int mois = Integer.parseInt(sparation[1]); int jour = Integer.parseInt(sparation[2]); Calendar calendrier = new GregorianCalendar(anne, mois-1, jour); return calendrier.getTime(); } }

entit.LotDePhotos.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQueries({ @NamedQuery(name="recherche", query="SELECT lot FROM LotDePhotos lot WHERE lot.titre = :titre"), @NamedQuery(name="toutLesLots", query="SELECT lot FROM LotDePhotos lot") }) public class LotDePhotos implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String titre; @JoinColumn @OneToMany(fetch= FetchType.EAGER, cascade= CascadeType.ALL) private List<Photo> photos = new ArrayList<Photo>(); public LotDePhotos() { } public LotDePhotos(String titre) { this.titre = titre; } public long getId() { return id; } public String getTitre() { return titre; } public void setTitre(String titre) { this.titre = titre; } public List<Photo> getPhotos() { return photos; } public void ajoutPhoto(Photo photo) { photos.add(photo); } @Override public String toString() { return titre; } }

entit.Photo.java
package entit; import java.io.Serializable; import java.util.Date; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity public class Photo implements Serializable { @Id private String id; private String description; private boolean exif; private double longitude; private double latitude; @Temporal(javax.persistence.TemporalType.DATE) private Date jour; public Photo() { id = System.currentTimeMillis()+".jpg"; } public Photo(String description) { id = System.currentTimeMillis()+".jpg"; this.description = description; } public String getId() { return id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Date getJour() { return jour; } public void setJour(Date jour) { this.jour = jour; } public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } public boolean isExif() { return exif; } public void setExif(boolean localisation) { this.exif = localisation; } public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } @Override

public String toString() { return description; } }

Scurisation des web services REST


La scurisation des applications est (ou devrait tre) un souci majeur pour les socits. Ceci peut aller de la scurisation d'un rseau au chiffrement des transferts de donnes, en passant par l'octroi de certaines permissions aux utilisateurs d'un systme. Au cours de notre navigation quotidienne sur Internet, nous rencontrons de nombreux sites o nous devons entrer un nom d'utilisateur et un mot de passe pour permettre l'accs certaines parties d'une application. La scurit est devenue une ncessit sur le Web et, en consquence, Java EE a dfini plusieurs mcanismes pour scuriser les applications. La scurit utilise le mcanisme d'autorisation galement connu sous le terme access control. Ce mcanisme permet d'associer des droits et permissions chaque utilisateur. Aprs authentification, le rle associ l'utilisateur dtermine les oprations qu'il est capable de faire. Le rle dfinit les parties des applications auxquelles l'utilisateur peut accder. Par exemple, tous les utilisateurs du groupe Enseignant peuvent accder au systme de gestion des notes et des cours alors que les utilisateurs associs au groupe Etudiant pouront seulement consulter la liste des notes. Un rle est diffrent pour chaque groupe mais un utilisateur peut tre associ plusieurs groupes. Les rles sont prciss dans le descripteur de dploiement de l'application /WEB-INF/web.xml. Les administrateurs de l'application affectent ensuite les rles chaque groupe partir d'un fichier XML, d'une base de donnes ou d'un annuaire LDAP.

Principal et rle
Les principaux et les rles tiennent une place importante dans la scurit logicielle. Un principal est un utilisateur qui a t authentifi (par un nom et un mot de passe stocks dans une base de donnes ou dans un fichier spcifique dans le serveur d'application, par exemple). Les principaux peuvent tre organises en groupes, appels rles, qui leur permettent de partager un ensemble de permissions (accs au systme de facturation ou possibilit d'envoyer des messages dans un workflow, par exemple). Comme vous pouvez le constater, un utilisateur authentifi est li un principal qui possde un identifiant unique et qui peut tre associ plusieurs rles. Ainsi le principal de l'utilisateur Jeanne, par exemple, est li aux rles de consultation, de cration et de modification.

Authentification et habilitation
La scurisation d'une application implique deux fonctions : l'authentification et l'habilitation. La premire consiste vrifier l'identit de l'utilisateur (son identifiant et son mot de passe, son empreinte biomtrique, etc.) en utilisant son systme d'authentification et en affectant un principal cet utilisateur. L'habilitation consiste dterminer si un principal (un utilisateur authentifi) a accs une ressource particulire (une photo, par exemple) ou une fonction donne (supprimer une photo, par exemple). Selon son rle, l'utilisateur peut avoir accs toutes les ressources, aucune ou certaines d'entre elles. Dans un scnario de scurit classique, l'utilisateur doit entrer son identifiant et son mot de passe via une interface client (web ou Swing). Ces informations sont vrifies avec JAAS (Java Authentication and Authorization Service) via un systme d'authentification sous-jacent. Si l'authentification russit, l'utilisateur est associ un principal qui est ensuite lui-mme associ un ou plusieurs rles. Lorsque l'utilisateur accde un EJB scuris (ou un composant REST scuris), le principal est transmis de faon transparente l'EJB, qui l'utilise pour savoir si le rle de l'appelant l'autorise accder aux mthodes qu'il tente d'excuter.

La scurit de java EE repose largement sur l'API JAAS qui est automatiquement intgre dans les serveurs d'applications certifis Java EE. En fait, JAAS est l'API utilise en interne par les couches web et EJB pour raliser des oprations d'authentification et d'habilitation. Elle peut accder galement aux systmes d'authentification sous-jacents comme LDAP, Active Directory, etc.

Cas d'utilisation o le client utilise un navigateur


Le protocole HTTP fonctionne sous la forme de requtes/rponses permettant de demander les identits des utilisateurs et de fournir l'accs aux ressources en fonction de ces identits. Lorsqu'une requte arrive destination d'une ressource protge, le serveur Web vrifie si le navigateur a envoy les informations d'authentification. Si tel n'est pas le cas, le serveur envoie une page d'erreur avec le code 401 au navigateur en prcisant le type d'information d'authentification qu'il attend. Le navigateur affiche

alors une bote de dialogue ou un formulaire d'authentification afin de demander les informations l'utilisateur. Si l'utilisateur est prcdemment authentifi, le navigateur utilise alors le cache local possdant les renseignements d'une page prcdente.

Scurisation avec Glassfish


Le systme d'authentification est prsent depuis le dbut du Web et a t dfini en 1996 dans la spcification HTTP 1.0. Le mcanisme d'authentification a galement introduit le concept de realm dfinit par la balise <realm-name /> dans le descripteur de dploiement WEB-INF/web.xml. Un Realm est une chane interprte par le seveur pour identifier une source de donnes afin de rsoudre les accs par identifiant et mot de passe. L'authentification prvue par Glassfish est base sur les Realms. Un Realm contient typiquement plusieurs utilisateurs et leurs informations de scurit. Les comptes utilisateurs sont bass sur des identifiants et des mots de passe.

Le serveur Java EE Glassfish dispose d'un mcanisme intgr trs souple pour grer les Realms. Il est possible d'utiliser le fichier domain.xml, le fichier de configuration spcifique au domaine, une base de donnes ou encore les donnes d'un serveur LDAP. Les rgles d'utilisation des Realms sont les suivantes : Tout utilisateur peut tre membre d'aucun ou plusieurs rles. Tous les rles peuvent contenir aucun ou plusieurs utilisateurs. Les Realms permettent de dfinir les utilisateurs qui peuvent accder des ressources en fournissant la liste d'un ou plusieurs rles disposant d'un droit d'accs. Une authentification base sur les Realms, galement appele security policy ou scurit de domaine, permet de dfinir la politique de scurit pour l'accs aux ressources. Le serveur Glassfish est livr avec trois Realms pr-configurs file, certificate et admin-realm. Deux sont bass sur des fichiers et un autre sur un certificat. Si une application ne prcise pas de Realm elle utilise le Realm file par dfaut.

Les types de Realms suivants sont utilisables avec GlassFish


file Le Realm par dfaut est nomm file. Ce realm stocke les utilisateurs, mots de passe et le groupe dans le fichier domains/domain1/config/keyfile.

La commande suivante liste tous les utilisateurs contenus dans le Realm de type file :

$ asadmin list-file-users
admin-realm Ce type de Realm est galement un Realm de type file mais sauvegarde les comptes administrateurs dans le fichier domains/domain1/config/admin-keyfile. certificat L'authentification est alors base sur les certificats clients. Le serveur utilise des comptes dans une base de donnes certifie. Avec ce type de scurit, le serveur utilise les certificats avec le protocole HTTPS pour l'authentification des clients. Glassfish utilise le format Java Key Store et stocke sa cl prive et ses certificats dans les fichiers domains/domain1/config/kestore.jsk et domains/domain1/config/cacerts.jsk. Les fichiers Java Key Store peuvent tre grs en utilisant l'outil Java SE keytool. ldap Ce type de Realm permet d'utiliser un annuaire LDAP pour les authentifications utilisateurs. jdbc Les Realms JDBC permettent de stocker les comptes utilisateurs dans une base de donnes relationnelle. Les Realms JDBC utilisent une ressource JDBC pour se connecter la base de donnes contenant les comptes utilisateurs.

La mise en place d'un Realm JBDC repose sur des paramtres JAAS. Ainsi, dans le paramtre JAAS context nous devons spcifier le nom de la classe pour le Realm JDBC (jdbcRealm). La proprit Digest Algorithm est ncessaire pour la gestion du mot de passe. L'algorithme de cryptage MD5 est souvent utilis pour spcifier le cryptage des mots de passe. Le paramtre Assign Group est utilis pour spcifier un groupe pour tous les utilisateurs.
custom Ce type trs souple permet de dfinir notre propre implmentation du Realm.

Actuellement, trois types d'authentification sont proposs :

Authentification de base Le schma d'authentification HTTP le plus simple se nomme autorisation de base (Basic Authorization). Ce mcanisme consiste utiliser un nom utilisateur/identifiant et un mot de passe en clair. Authentification par digest Avec ce type d'autorisation, l'utilisateur saisie son mot de passe en clair, mais celui-ci est envoy sous forme cripte partir d'une chane de caractres sur le rseau. Cette authentification est appele authentification par Digest (Digest Auth) et utilise l'algorithme de hachage unilatral SHA ou MD5. Authentification par certificat Avec ce type d'authentification, l'utilisateur doit possder un certificat client pour accder au serveur. Cette approche est la plus sre puisque les certificats sont grs de faon centralise par des autorits spcialises.

Les types d'authentification Realm


Il existe plusieurs types d'authentification pouvant tre utiliss pour les Realms. Dans une application, l'lment <realm-name /> du fichier de configuration WEB-INF/web.xml permet de spcifier quel Realm doit tre utilis pour authentifier l'utilisateur. Les applications Web peuvent utiliser les types :
BASIC L'authentification est base sur une bote de saisie de type identifiant / mot de passe. Les protocoles supports sont HTTP et HTTPS.

DIGEST L'authentification est proche de la prcdente mais le mot de passe client est crypt avant l'envoi par le navigateur. FORM L'authentification est base sur un formulaire de saisie de type identifiant / mot de passe personnalisable et dvelopp en XHTML, supportant les protocoles HTTP et HTTPS. CLIENT-CERT L'authentification est base sur un certificat cl publique. La communication est effectue au travers des protocoles HTTP et HTTPS.

Premire mise en oeuvre d'un systme de scurit avec Glassfish


Aprs tout ce prambule, je vous propose de valider toutes ces comptences nouvellement acquises en reprenant le projet du service web REST d'archivage de photos afin d'y associer un systme de scurit bas sur les realms.

Cration d'un son propre realm


Ce type de scurit est utilise avec l'interface d'administration de GlassFish accessible l'adresse URL suivante http://localhost:4848/. Je profite de l'occasion pour gnrer un nouveau realm qui sera utilis uniquement par ce service Web. Les captures d'cran ci-dessous vous montre l'ensemble des enchanements.

Retour sur le projet d'archivage de photos


Nous allons donc scuriser l'application web qui contient le web service REST d'archivage de photos. Je vous rappelle ci-dessous le code concernant ce service avec quelques petites modifications mineures.

service.Archivage.java
package service; import java.io.*; import javax.annotation.PostConstruct; import javax.ws.rs.*; @Path("/") public class Archivage { private final String rpertoire = "ArchivagePhotos/"; @PostConstruct private void init() { File rep = new File(rpertoire); if (!rep.exists()) rep.mkdir(); } @GET @Path("consultation/liste") @Produces("application/json") public String[] getPhotos() { return new File(rpertoire).list(); } @GET @Path("consultation/{nomFichier}") @Produces("image/jpeg") public InputStream restituer(@PathParam("nomFichier") String nom) throws FileNotFoundException {

return new FileInputStream(rpertoire+nom); } @POST @Path("gestion/{nomFichier}") @Consumes("image/jpeg") public void stocker(@PathParam("nomFichier") String nom, InputStream flux) throws IOException { byte[] octets = lireOctets(flux); FileOutputStream fichier = new FileOutputStream(rpertoire+nom); fichier.write(octets); fichier.close(); } @PUT @Path("gestion/ancien={ancien}/nouveau={nouveau}") public void changerNomPhoto(@PathParam("ancien") String ancien, @PathParam("nouveau") String nouveau) { File rep = new File(rpertoire); File ancienFichier = new File(rep+"/"+ancien); File nouveauFichier = new File(rep+"/"+nouveau); ancienFichier.renameTo(nouveauFichier); System.out.println(ancienFichier.getPath()); System.out.println(nouveauFichier.getPath()); } @DELETE @Path("gestion/{nomFichier}") public void supprimer(@PathParam("nomFichier") String nom) { new File(rpertoire+nom).delete(); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } }

Mise en application de photos-realm


Le modle de scurit Java EE propose des accs privilgis certaines ressources en association avec des rles au travers du mcanisme des realms. Pour raliser cette opration, les crires suivants sont prendre en considration : Dans une application, l'lment <realm-name /> du fichier de configuration WEB-INF/web.xml permet de spcifier quel realm doit tre utilis pour authentifier l'utilisateur. Le contrle d'accs est bas sur les rles dclars sous la forme de balises <role-name /> dans le descripteur de dploiement (ou dclars dans le code avec l'annotation @DeclareRoles que nous exploiterons ultrieurement). Le descripteur de dploiement spcifique au serveur (WEB-INF/glassfish-web.xml) doit ensuite fournir le mapping entre les utilisateurs et groupes du realm ainsi que les rles dfinis dans l'application. Les informations confidentielles peuvent tre changes avec le protocole Secure Socket Layer (SSL) et son successeur Transport Level Security (TSL). Comme mentionn prcdemment, le principe d'autorisation Java EE est base sur le mcanisme des rles. L'accs une ressource par un utilisateur dpend du mapping de cet utilisateur ou de son groupe vers un rle associ. Ainsi, le descripteur de dploiement spcifique au serveur WEB-INF/glassfish-web.xml spcifie le mapping entre les utilisateurs (ou les groupes) dans le realm choisi et les rles de l'application. Ceci est ralise au travers de la balise <security-role-mapping>.

glassfish-web.xml
<?xml version="1.0" encoding="UTF-8"?> <sun-web-app error-url=""> <context-root>/Photos</context-root> <security-role-mapping> <role-name>gestion</role-name> <group-name>creation</group-name> <group-name>modification</group-name> </security-role-mapping> <security-role-mapping> <role-name>recuperer</role-name> <group-name>consultation</group-name> </security-role-mapping> <security-role-mapping> <role-name>supprimer</role-name> <principal-name>manu</principal-name> </security-role-mapping> </sun-web-app> Quand nous avons cr photos-realm, nous avons dfini un certain nombre d'utilisateurs associs un ensemble de groupes, respectivement, pour notre exemple, les utilisateurs manu, jeanne et martin avec les groupes consultation, creation, modification et suppression. Cette notion de groupe est souvent associe la notion de rle. Toutefois, il est possible de faire la distinction pour une application donne. C'est d'ailleurs souvent le concept qui est retenu dans le systme de scurit des serveurs d'applications implmentant Java EE. C'est dans cette dmarche que le descripteur de dploiement spcifique au serveur glassfish-web.xml est conu. Il dfinit les rles attendus cette application Web et les associent des utilisateurs ou des groupes d'utilisateurs. Ainsi, dans notre exemple, nous devons prendre en compte trois rles spcifiques, gestion, recuperer et supprimer. Puisque nous avons trois cas particulier, nous devons donc proposer trois balises <security-role-mapping> qui vont raliser le mapping entre les rles de l'application dfinis par les balises <role-name> et les utilisateurs ou les groupes au travers respectivement des balises <principal-name> et <group-name>. Une fois que nous connaissons les diffrents rles ncessaires, nous pouvons grer de faon prcise la scurit associe cette application web dans le descripteur de dploiement WEB-INF/web.xml. Trois parties sont ncessaires pour exploiter pleinement la scurit, le mode d'authentification au travers de la balise <login-config>, tous les rles utiliss par cette

application avec la balise <security-role> et enfin les contraintes d'utilisation pour chaque rle au travers de la balise <security-constraint>.

web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>Gestion des photos</web-resource-name> <url-pattern>/gestion/*</url-pattern> <http-method>POST</http-method> <http-method>PUT</http-method> </web-resource-collection> <auth-constraint> <role-name>gestion</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Suppression de photos</web-resource-name> <url-pattern>/gestion/*</url-pattern> <http-method>DELETE</http-method> </web-resource-collection> <auth-constraint> <role-name>supprimer</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Rcupration de photo</web-resource-name> <url-pattern>/consultation/*</url-pattern> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <role-name>recuperer</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>gestion</role-name> <role-name>recuperer</role-name> <role-name>supprimer</role-name> </security-role> <login-config> <auth-method>BASIC</auth-method> <realm-name>photos-realm</realm-name> </login-config> </web-app> Ainsi, l'aide de la balise <login-config>, nous pouvons spcifier le realm prendre en compte avec la balise <realm-name> et de prciser le type d'authentification attendu l'aide de la balise <auth-method>, ici le mode basique. Nous dfinissons ensuite tous les rles prendre en compte au travers de la balise <security-role> l'intrieur de laquelle va se trouver l'ensemble des balises <rolename>. La grosse partie se situe dans la dfinissions des contraintes de scurit associes chaque rle spcifi, au travers de la balise <security-constraint>. A l'intrieur de cette balise se trouvent un certain nombre d'autres balises qui vont nous permettre de rgler prcisment les filtres d'utilisation pour chacun de ces rles. <web-resource-collection> : cette balise permet de dcrire les contarintes d'accs aux ressources spcifies. <web-resource-name> : cette balise permet de donner un nom au systme d'authentification. <url-pattern> : cette balise permet de dfinir, partir d'une expression rgulire, les ressources protger. Il doit y avoir zro ou plusieurs balises <url-pattern> dans une balise <web-ressource-collection>. L'abscence de la balise <url-pattern> indique que la contrainte de scurit doit s'appliquer toutes les ressources. <http-method> : il est possible de proposer un filtre sur le choix des mthodes HTTP. L aussi, cette balise est optionnelle et nous pouvons en avoir plusieurs. <auth-constraint> : cette balise contient zro ou plusieurs balises <role-name> afin de prciser le ou les rles ayant accs aux ressources. <role-name> : cette balise permet de dfinir le nom du rle ayant accs aux ressources scurises.

Utilisation de cette application web scurise

Authentification l'aide d'un client Android


Lorsque nous avons un navigateur, nous sommes habitu grer les authentifications. Quand est-il d'un client quelconque qui interroge le service web REST ? et plus spcifiquement quand est-il d'un client Android ? Je vais m'intresser plus particulirement au client Android sachant que si une autre application cliente implmente la librairie HttpClient, la rponse sera totalement traite de la mme faon. En effet HttpClient 4.x de la librairie Apache supporte les authentifications Basic, Digest et Certificat. Ces authentifications s'implmentent l'aide de la mthode setCredentials() elle-mme issue de la mthode getCreadentialsProvider() elle mme contenue dans la classe DefaultHttpClient. public void envoyer(View vue) throws IOException { DefaultHttpClient client = new DefaultHttpClient(); client.getCredentialsProvider().setCredentials( new AuthScope(adresse, 8080), new UsernamePasswordCredentials(utilisateur, motdepasse)); HttpPost requete = new HttpPost("http://"+adresse+":8080/Photos/gestion/"+description.getText().toString()+".jpg"); requete.setEntity(new FileEntity(new File(Environment.getExternalStorageDirectory(), "photo.jpg"), "image/jpeg")); client.execute(requete); } La classe org.apache.http.auth.AuthScope permet de dfinir le serveur atteindre avec son numro de service. Vous pouvez spcifier le serveur par son nom DNS ou par son adresse IP. La classe org.apache.http.auth.UsernamePasswordCredentials vous vous en doutez, permet de donner l'authentification proprement dit avec le nom d'utilisateur associ son mot de passe. Vous pouvez ainsi appeler la mthode setCredentials() chaque fois que vous aurez besoin de communiquer avec un service scuris. Je vous redonne le code complet de l'application cliente Android qui permet de prendre une photo et de l'envoyer ensuite au service REST d'archivage, avec en plus les modifications qui tiennent compte de l'authentification.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.photos" android:versionCode="1" android:versionName="1.0"> <application android:label="Stockage photos" > <activity android:name="ArchivagePhotos" android:label="Stocker vos photos" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="ChoixServeur" android:label="Choix du serveur" android:theme="@android:style/Theme.Dialog" /> </application> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest>

res/layout/adresse.xml
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="3dp"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="3dp"> <Button android:id="@+id/ok" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" android:onClick="ok"/> <EditText android:layout_toLeftOf="@id/ok" android:id="@+id/adresse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Adresse IP" /> </RelativeLayout> <EditText android:id="@+id/utilisateur" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Utilisateur" /> <EditText android:id="@+id/motdepasse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:password="true" android:hint="Mot de passe" /> </LinearLayout>

fr.btsiris.rest.ChoixServeur.java
package fr.btsiris.rest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.*; import android.widget.EditText; public class ChoixServeur extends Activity { private EditText adresse; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.adresse); adresse = (EditText) findViewById(R.id .adresse); } public void ok(View vue) { Intent intent = new Intent(); intent.putExtra("adresse", adresse.getText().toString()); setResult(RESULT_OK, intent); finish(); } }

res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#008800" android:padding="3dp"> <EditText android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Description de la photo" android:layout_alignParentBottom="true"/> <LinearLayout android:id="@+id/boutons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@id/description"> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Photographier" android:onClick="photographier" /> <Button android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="Envoyer" android:onClick="envoyer" /> </LinearLayout> <ImageView android:id="@+id/image" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:layout_above="@id/boutons" /> </RelativeLayout>

fr.btsiris.rest.ArchivagePhotos.java
package fr.btsiris.photos; import android.app.Activity; import android.content.Intent; import android.graphics.*; import android.net.Uri; import android.os.*; import android.provider.MediaStore; import android.view.View; import android.widget.*; import java.io.*; import org.apache.http.auth.*; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.FileEntity; import org.apache.http.impl.client.DefaultHttpClient; public class ArchivagePhotos extends Activity { private EditText description; private ImageView image; private Uri fichierUri; private String adresse, utilisateur, motdepasse; private final int RECHERCHE_ADRESSE = 1; private final int PHOTOGRAPHIER = 2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); description = (EditText) findViewById(R.id .description); image = (ImageView) findViewById(R.id .image); startActivityForResult(new Intent(this, ChoixServeur.class), RECHERCHE_ADRESSE); }

@Override protected void onActivityResult(int requestCode, int resultCode, Intent intention) { switch (requestCode) { case RECHERCHE_ADRESSE : adresse = intention.getStringExtra("adresse"); utilisateur = intention.getStringExtra("utilisateur"); motdepasse = intention.getStringExtra("motdepasse"); break; case PHOTOGRAPHIER : File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); Bitmap photo = BitmapFactory.decodeFile(fichier.getPath()); image.setImageBitmap(photo); break; } } public void photographier(View vue) { Intent intention = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); fichierUri = Uri.fromFile(fichier); intention.putExtra(MediaStore.EXTRA_OUTPUT, fichierUri); startActivityForResult(intention, PHOTOGRAPHIER); } public void envoyer(View vue) throws IOException { DefaultHttpClient client = new DefaultHttpClient(); client.getCredentialsProvider().setCredentials( new AuthScope(adresse, 8080), new UsernamePasswordCredentials(utilisateur, motdepasse)); HttpPost requete = new HttpPost("http://"+adresse+":8080/Photos/gestion/"+description.getText().toString()+".jpg"); requete.setEntity(new FileEntity(new File(Environment.getExternalStorageDirectory(), "photo.jpg"), "image/jpeg")); client.execute(requete); } }

Optimiser la scurisation avec HTTPS


L'exemple de scurisation que nous venons de traiter peut convenir dans la majorit des cas. Toutefois, il existe un petit problme, c'est que le mot de passe est envoy en clair et qu'il peut donc tre vue au travers d' un analyseur de trame.

Le serveur GlassFish supporte les protocoles SSL et TLS pour les changes scuriss. Pour utiliser SSL, GlassFish doit avoir un certificat pour chaque interface externe ou adresse IP acceptant les connexions scurises. Par dfaut, HTTPS est activ sur le port 8181 pour le trafic Web, et SSL est activ sur les ports 3820 et 3902 pour le trafic IIOP.