Avec la plateforme Java EE, nous avons découvert de nombreux services différents, qui sont très faciles à développer (codage extrêmement simplifié), grâce à la technique des objets distants.
Dans un premier temps, nous avons utiliser les beans sessions qui s'intéressent à la logique métier. Ce sont des objets distants facile à manipuler. Effectivement, avec une application fenêtrée classique, il suffit d'appeler les méthodes de ces objets comme s'ils étaient présent sur le poste local. Par contre, pour que ce fonctionnement simple puisse s'établir, vous devez rester dans le réseau local de l'entreprise (le numéro de port de ces services va être bloqué par le pare-feu).
Si vous désirez utiliser ces services depuis Internet, nous avons découvert que nous devions passer par une application Web qui elle-même communique, en local, aux différents beans sessions. Cela fonctionne très bien, mais nous devons passer systématiquement par un navigateur Web, ce qui limite beaucoup la présentation et l'ergonomie (il s'agit d' un client léger). Effectivement, nous travaillons uniquement à l'aide de page Web.
Le top du top serait de pouvoir faire comme en réseau local, c'est-à-dire de pouvoir utiliser une application fenêtrée, qui fait appel aux différents 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 réalité, 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 différentes méthodes de l'objet distant se fait à l'aide d'un document XML, qui est envoyé et interprété. 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 démarche, c'est que vous pouvez développer votre service Web avec le langage que vous voulez, de même que pour l'application cliente. Ainsi par exemple, vous pouvez faire votre service web à l'aide de la plate-forme Java EE et développer votre application cliente en .NET. C'est ce que nous appelons : Interopérabilité.
Nous imaginons aisément que toute cette technique est compliquée. Effectivement elle l'est. Toutefois, grâce aux outils de développement comme NetBeans, la fabrication d'un service Web est très facile à mettre en oeuvre. Toute la logique de bas niveau est automatiquement gérée. 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 réduit considérablement toute la complexité en s'occupant automatiquement de toute l'ossature de bas niveau.
Avant d'expliquer ce qu'est un service web (Web Service), nous allons voir pourquoi ils existent. Auparavant, pour mettre en place des applications distribuées, les développeurs devaient mettre en place des architectures type Corba dans le cas d'applications hétérogènes, RMI en environnement Java ou encore DCOM chez Microsoft. Pour faciliter la coordination de différents systèmes hétérogènes, les grands éditeurs comme SUN, IBM, Oracle... ont décidé d'établir un standard de communication : les services web. Les services web permettent ainsi, ce que nous avons nommé, l'interopérabilité.
Pour résumer, les services web sont une sorte de logique métier offerte à une application cliente (c'est-à-dire un consomateur de service) via une interface de service. A la différence des objets ou des EJBs, les services web fournissent une interface faiblement couplée en se servant de XML.
Les standards précisent que l'interface à laquelle nous envoyons un message doit définir le format du message de requête et de réponse, ainsi que les mécanismes pour publier et pour découvrir les interfaces du service (une base de registres).
Nous découvrons ici l'interaction d'un service
web de façon très 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
connaît l'interface du service et le format du message, il peut envoyer
une requête et recevoir une réponse.
Les services web nécessitent plusieurs technologies et protocoles pour transporter et transformer les données d'un client vers un service de façon standard. Les plus courants sont les suivants :
L'intérêt des services web réside dans la robustesse du protocole HTTP et donc sa capacité à passer plus facilement les pare-feu qu'un autre protocole.
Pour offrir un service web avec SOAP il nous faut :
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 standardisée permettant de trouver les informations sur un service web et sur la façon 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 métier, sa localisation géographique, le site web, le numéro de téléphone, etc.
Les autres métiers peuvent ensuite parcourir cette base et retrouver les informations sur un servcie web spécifique, qui contiennent des métadonnées supplémentaires décrivant son comportement et son emplacement. Ces informations sont stockées sous la forme de document WSDL : les clients peuvent lire ce document afin d'obtenir l'information et invoquer le service.
La base de registre UDDI pointe vers un fichier WSDL sur Internet, qui peut être téléchargé par les consomateurs potentiels. WSDL est un langage de définition d'interfaces permettant de définir les interactions entre les consomateurs et les services.
C'est donc le composant central d'un service web
puiqu'il décrit le type de message, le port, le protocole de
communication, les opérations possibles, son emplacement et ce que le
client peut en attendre. Vous pouvez considérer WSDL
comme une interface Java, mais écrite en XML.
Pour garantir l'interopérabilité, l'interface standard du service web doit être standardisée, afin qu'un consomateur et un producteur puissent partager et comprendre un message. C'est le rôle de WSDL. SOAP, de son côté, définit la façon dont le message sera envoyé d'un ordinateur à l'autre.
En réalité, WSDL est scindé en deux parties que nous appelons abstraite et concrète. La signature du service, ses méthodes et ses paramètres sont décrits de manière abstraite. Cette partie est ensuite liée à un protocole de communication et à un format de messages concrets. Ainsi, la partie abstraite est totalement découplée de la manière concrète permettant d'appeler le service.
<?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-tête <definitions>. Cet élément peut prendre plusieurs attributs facultatifs qui définissent des noms de domaines dans la suite du document. Dans notre exemple, la définition reçoit le nom StockerPhotosService. Le service web portant ce même nom (<service>) peut être invoqué à partir de l'URL spécifiée (http://portable:8080/StockerPhotosService/StockerPhotos)
Nous ne nous attarderons pas sur WSDL car, comme vous le découvrirez plus loin, ce document est généré automatiquement et n'a pas à être développé manuellement.
SOAP est le protocole standard des services web. Il fournit le mécanisme de communication permettant de connecter les services qui échangent des données au format XML au moyen du protocole réseau - HTTP le plus souvent. Comme WSDL, SOAP repose fortement sur XML : un message SOAP est un document XML contenant plusieurs éléments (une enveloppe, un corps, etc. ).
SOAP permet ainsi la transmission de messages entre objets distants, en invoquant des méthodes sur des objets physiquement situés sur une autre machine. Le transfert se fait le plus souvent à l'aide du protocole HTTP.
Le protocole SOAP se décompose en deux parties :
Voici deux captures de trames qui visualisent l'appel
d'une méthode stocker() avec sa réponse.
Les services web envoient des requêtes et des réponses en échangeant des messages XML. En Java, il existe plusieurs API de bas niveau pour traiter les documents XML et les schémas XML. La spécification JAXB fournit un ensemble d'API et d'annotations pour représenter les documents XML comme des artéfacts Java représentant des documents XML.
JAXB facilite la
désérialisation des documents XML en objets
et leur sérialisation en documents XML. Même
si cette spécification peut être utilisée pour n'importe quel traitement XML, elle est fortement intégrée aux services web.
Pour en savoir plus sur JAXB.
JAX-WS définit 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 requêtes 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 dépend lui-même d'autres spécifications comme JAXB que nous venons de découvrir.
JAX-WS est la nouvelle appellation de JAX-RPC (Java API for XML Based RPC) qui permet de développer très 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 représenter 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 générer ou de parser les messages SOAP. JAX-WS s'occupe de ces traitements de bas niveau.
@WebService() public class StockerPhotos { @WebMethod public void stocker(String nomFichier, byte[] octets) { ... } ... }
Malgré tous ces concepts, spécifications, standards et organisations, l'écriture et la consommation d'un service web sont très très simples.
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 requête = bd.createNamedQuery( ); return requête.getResultList(); } }
Comme les entités ou les EJB, un service web utilise le modèle de classe annoté avec une politique de configuration par exception. Si tous les choix par défaut vous conviennent, ceci signifie qu'un service web peut se réduire à une simple classe Java annotée @javax.ws.WebService.
Le service GestionPersonnel propose plusieurs méthodes pour gérer 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
décrit l'architecture d'un service web, nous avons vu que les données
échangées devaient être des documents XML :
nous avons donc besoin d'une méthode 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 réciproquement.
Grâce aux annotations JAXB,
il n'est pas nécessaire 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 crée une instance de Personne
puis invoque le service web, comme dans le code suivant :
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 prénom = new JTextField(); private JFormattedTextField âge = new JFormattedTextField(0); private JComboBox téléphones = 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( ); add(outils, BorderLayout.NORTH); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { personne = new Personne(nom.getText(), prénom.getText(), (Integer)âge.getValue()); for (int i=0; i<téléphones.getItemCount(); i++) personne.getTelephones().add((String) téléphones.getItemAt(i)); gestion.nouveau(personne); listingPersonnes(); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { String téléphone = (String) téléphones.getSelectedItem(); téléphones.addItem(téléphone); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { personne.modifier(nom.getText(), prénom.getText(), (Integer)âge.getValue()); gestion.modifier(personne); listingPersonnes(); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { gestion.supprimer(personne); listingPersonnes(); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { personne = new Personne(); rafraîchir(); } }); panneau.setLayout(new GridLayout(4, 2)); panneau.add(new JLabel( )); panneau.add(nom); panneau.add(new JLabel( )); panneau.add(prénom); panneau.add(new JLabel( )); panneau.add(âge); panneau.add(new JLabel( )); panneau.add(téléphones); téléphones.setEditable(true); add(panneau); add(liste, BorderLayout.SOUTH); liste.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) { personne = (Personne) liste.getSelectedItem(); rafraîchir(); } } }); 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 rafraîchir() { nom.setText(personne.getNom()); prénom.setText(personne.getPrenom()); âge.setValue(personne.getAge()); téléphones.removeAllItems(); for (String téléphone : personne.getTelephones()) téléphones.addItem(téléphone); } public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } }
Bien que ce code soit très simple à comprendre, beaucoup de choses se passent en arrière-plan. Pour que tout ceci fonctionne, plusieurs artéfacts ont été générés automatiquement : un fichier WSDL et des stubs clients qui contiennent toutes les informations pour se connecter à l'URL de service web, la sérialisation de l'objet Personne en XML, l'appel du service web et la récupération des différents résultats.
La partie visible des services web en Java ne manipule pas directement XML, SOAP ou WSDL. Elle est donc très simple à comprendre. Cependant, certaines parties invisibles sont très importantes pour l'interopérabilité.
Comme vous l'avez compris, XML est utilisé pour échanger les données et définir les services web via WSDL et les enveloppes SOAP. Pourtant, dans le code précédent, 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, gèrent toute la plomberie XML et les connexions réseau.
Nous manipulons des classes Java à un endroit de la chaîne et des documents XML à un autre - le rôle de JAXB est de faciliter cette correspondance bidirectionnelle.
J'ai déjà 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 préfère vous repréciser ici.
JAXB définit un standard permettant de lier les représentations Java à XML et réciproquement. Il gère les documents XML et les définitions des schémas XML (XSD) de façon transparente et orientée objet qui masque la complexité du langage XSD.
Grâce à cette simple annotation et à un mécanisme de sérialisation, JAXB est capable de créer une représentation XML d'une instance de Personne.
<?xml version= encoding= standalone= ?> <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 sérialisation, ici, consiste à transformer un objet en XML, mais JAXB permet également de faire l'inverse : la désérialisation qui prend ce document XML en entrée et crée un objet Personne à partir des valeurs de ce document.
JAXB peut produire automatiquement le schéma qui valide automatiquement la structure XML du personnel afin de garantir qu'elle est correcte et que les types des données conviennent.
<?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 schéma XML (XSD) de la classe Personne. Ce schéma est constitué uniquement d'éléments simples. Vous remarquez que tous les marqueurs sont préfixés par xs (xs:int, xs:long, xs:string, etc.) : ce préfixe est un espace de noms et est défini dans l'élément xmlns (XML namespace) du marqueur d'en-tête du document.
Les espaces de noms créent des préfixes uniques pour les éléments des documents ou des applications qui sont utilisés ensemble. Leur but principal consiste à éviter les conflits qui pourraient survenir lorsqu'un même nom d'élément apparaît dans plusieurs documents. Ceci peut poser un problème non négligeable pour les servcies web car ils manipulent plusieurs documents en même temps (l'enveloppe SOAP, le document WSDL, etc.) : les espaces de noms sont donc très importants pour les services web.
L'API JAXB, définie 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 modèles. Le framework d'exécution de JAXB implémente les opérations de sérialisation et de désérialisation.
Les données XML sérialisées peuvent être validées par un schéma XML - JAXB peut produire automatiquement ce schéma à partir d'un ensemble de classes et vice versa.
JAXB fournit également un compilateur de schémas (xjc) et un générateur de schéma (schemagen) - alors que la sérialisation/désérialisation manipule des objets et des documents XML, ce compilateur et ce générateur de schémas manipulent des classes et des schémas XML.
Par bien des aspects, JAXB ressemble à JPA. Cependant, au lieu de faire correspondre les objets à une base de données, JAXB les lie à un document XML. Comme JPA, JAXB définit 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 alléger le travail du développeur.
@XmlRootElement public class Personne { private long id; private String nom; private String prenom; private int age; ... }
L'annotation @XmlRootElement prévient JAXB que la classe Personne est l'élément racine du document XML. Si cette annotation est absente, JAXB lancera une exception lorsqu'il tentera de sérialiser la classe. Cette dernière est ensuite associée au schéma ci-dessous en utilisant les associations par défaut (chaque attribut est traduit en un élément de même nom)
<?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 aisément une représentation XML d'un objet de type Personne. L'élément racine <personne> représente l'objet Personne et inclut la valeur de chaque attribut de la classe.
<?xml version= encoding= standalone= ?> <personne> <id>51</id> <nom>REMY</nom> <prenom>Emmanuel</prenom> <age>31</age> </personne>
Le code ci-dessous utilise ces annotations spécifiques pour transformer le numéro d'identifiant en attribut (au lieu d'un élément par défaut) et pour renommer l'ensemble des balises du document XML.
@XmlRootElement public class Personne { @XmlAttribut(required = true) private long id; @XmlElement(name = "Nom") private String nom; @XmlElement(name = "Prénom") private String prenom; @XmlElement(name = "Âge", defaultValue = "21") private int age; ... }
Cette classe sera donc liée à un schéma différent dans lequel le numéro d'identifiant est un <xs:attribute> obligatoire et l'âge renommé possède une valeur par défaut de 21.
<?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="Prénom" type="xs:string" minOccurs="0" /> </xs:sequence> <xs:attribute name="id" type="xs:long" use="required" /> </xs:complexType>
</xs:schema>
<?xml version= encoding= standalone= ?> <personne id="51"> <Nom>REMY</Nom> <Prénom>Emmanuel</Prénom> <Âge>21</Âge> </personne>
Le tableau ci-dessous énumère les principales annotations de JAXB ; la plupart avec les éléments 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 | Description |
---|---|
XmlAccessorOrder | Contrôler l'ordre des attributs et des propriétés dans la classe. |
XmlAccessorType | Contrôler si l'attribut ou la propriété de la classe est sérialisé par défaut. |
XmlAnyAttribute | Convertir une propriété de la classe en un ensemble d'attributs de type jocker dans le document XML. |
XmlAnyElement | Convertir une propriété de la classe en une description representant l'élément JAXB dans le document XML. |
XmlAttachmentRef | Marquer un attribut ou une propriété de la classe comme une URI que fait référence à une type MIME particulier. |
XmlAttribute | Convertir une propriété de la classe en un attribut dans le document XML. |
XmlElement | Convertir une propriété de la classe en un élément dans le document XML. |
XmlElementDecl | Associer une fabrique à un element XML. |
XmlElementRef | Convertir une propriété de la classe en un élément dans le document XML qui est associé à un nom de propriété particulier. |
XmlElementRefs | Marquer une propriété de la classe qui fait référence aux classes qui possèdente XmlElement ou JAXBElement. |
XmlElements | Créer un conteneur pour de multiples annotations XmlElement, qui précise ainsi le choix possible parmi celles qui sont proposées. |
XmlElementWrapper | Créer un élément père dans le document XML pour des collections d'éléments. |
XmlEnum | Convertir une énumération en une représentation équivalente dans le document XML. |
XmlEnumValue | Convertir un énumérateur en une représentation équivalente dans le document XML. |
XmlID | Convertir une propriété de la classe en un ID XML. |
XmlIDREF | Convertir une propriété de la classe en un IDREF XML. |
XmlInlineBinaryData | Encoder en base64 dans le document XML. |
XmlList | Utilisé pour convertir une propriété de la classe vers une liste de type simple. |
XmlMimeType | Associer un type MIME qui contrôle la représentation XML en rapport avec la propriété de la classe. |
XmlMixed | Annoter une propriété de la classe qui peut supporter plusieurs types de valeur avec un contenu mixte. |
XmlNs | Associer un prefix d'un espace de nommage à une URI. |
XmlRegistry | Marquer une classe comme possédant une ou des méthodes annotées avec XmlElementDecl. |
XmlRootElement | Associer une classe ou une énumération à un element XML. Très souvent utilisé pour spécifier la racine du document XML. |
XmlSchema | Associer un espace de nommage à un paquetage. |
XmlSchemaType | Associer un type Java ou une énumeration à un type défini dans un schéma. |
XmlSchemaTypes | Conteneur pour plusieurs propriétés annotées par XmlSchemaType. |
XmlTransient | Marquer une entité pour qu'elle ne soit pas mappée dans le document XML. |
XmlType | Convertir une classe ou une énumération vers le type spécifié dans Shéma XML correspondant. |
XmlValue | Mapper une classe vers le type complexe dans le Schéma XML ou vers le type simple suivant le cas. |
Même si nous ne manipulons pas explicitement des documents SOAP et WSDL lorsque nous développons avec JAX-WS, il est important de comprendre un peu leur structure. Les services web fournissent deux ingrédients essentiels :
Lorsqu'un consommateur invoque le service web GestionPersonnel,
il récupère son WSDL pour connaître son
interface et il demande à connaître la liste de tous les agents (le
message listePersonnels de SOAP)
et reçoit une réponse (le message listePersonnelsResponse
de SOAP).
Les documents WSDL sont hébergés dans le conteneur de service web et utilisent XML pour décrire ce que fait un service, comment appeler ses opérations et où le trouver. Ce document XML respecte une structure bien établie, formée de plusieurs parties. Le sercice GestionPersonnel, par exemple, utilise les éléments suivants :
<?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'élément <xsd:import
namespace>
fait référence à un schéma XML qui doit être
disponible sur le réseau pour les clients du WSDL.
.
Alors que WSDL décrit une interface abstraite du service web, SOAP fournit une implémentation concrète en définissant la structure XML des messages échangés. Dans le cadre du Web, SOAP est une structure de messages pouvant être délivrés par HTTP (ou d'autres protocoles de communication) - la liaison HTTP de SOAP contient quelques en-têtes d'extension HTTP standard.
Cette structure de message est décrite en XML. Au lieu d'utiliser HTTP pour demander une page web à partir d'un navigateur, SOAP envoie un message XML via une requête HTTP et reçoit une réponse HTTP. Un message SOAP est un document XML contenant les éléments suivants :
Seuls l'enveloppe et le corps sont obligatoires. Dans notre
exemple, une application cliente appelle le service web pour connaître
l'ensemble du personnel de l'entreprise (une enveloppe SOAP
pour la requête) et reçoit la liste des
agents (une autre enveloppe SOAP pour la réponse).
Nous venons de découvrir un document WSDL, ainsi qu'une requête et une réponse SOAP. Vous avez pu remarquer que lorsque les services web proposent plusieurs opérations avec des pararamètres complexes, ces documents XML deviennent un véritable cauchemar pour le développeur ; 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 méthode descendante, également méthode par contrat car elle part du contrat (le WSDL) en définissant les opérations, les messages, etc. Lorsque le consommateur et le fournisseur sont d'accord sur le contrat, nous pouvons implémenter 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 méthode ascendante, la classe de l'implémentation existe déjà et il suffit de créer le WSDL. Là encore, Metro dispose d'outils pour effectuer cette opération. 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 : grâce à un modèle de développement simple et à quelques annotations, vous pouvez ajuster l'association Java-WSDL.
L'approche ascendante peut produire des applications très inefficaces car les méthodes et les classes Java n'ont aucune idée de la granularité idéale des messages qui circulent sur le réseau. Si la latence est élevée et/ou la bande passante, faible, il est préférable d'utiliser moins de messages, qui seront plus gros : seule l'approche par contrat permet de faire ce choix.
Toute cette étude nous a présenté les concepts et les spécifications des services web en général puis a introduit JAXB et a effleuré la surface des documents WSDL et SOAP. Cependant, les services web suivent le paradigme de facilité de développement de Java EE 6 et n'obligent pas à écrire le moindre code WSDL ou SOAP. Un service web est une simple classe annotée qui doit être déployée dans un conteneur de service web. Toutefois, ce modèle de programmation mérite notre attention.
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 nécessaire pour transformer une simple classe en service web, mais cette classe doit respecter les règles suivantes :
Au niveau du service, les systèmes sont définis en terme de messages XML, d'opérations WSDL et de messages SOAP. Cependant, au niveau Java, les applications sont décrites en termes d'objets, d'interfaces et de méthodes. Il est donc nécessaire d'effectuer une traduction des objets Java vers les opérations WSDL. L'environnement d'exécution de JAXB utilise les annotations pour savoir comment sérialiser/désérialiser une classe vers/à partir de XML.
L'annotation @javax.jws.WebService marque une classe ou une interface Java comme étant un service web. Cette annotation possède un certain nombre d'attributs permettant de personnaliser le nom du services web dans le fichier WSDL (éléments <wsdl:portType> ou <wsdl:service>) et son espace de noms ainsi que l'emplacement du fichier WSDL lui-même (attribut wsdlLocation).
@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 ; }
Lorsque nous utilisons @WebService, toutes les méthodes publiques du service web qui n'utilisent pas l'annotation @WebMethod sont exposées.
Par défaut, toutes les méthodes publiques d'un service web sont exposées dans le WSDL et utilisent toutes les règles d'association par défaut. L'annotation @javax.jws.WebMethod permet de personnaliser certaines de ces associations de méthodes. Son API est assez simple et permet de renommer une méthode ou de l'exclure du WSDL.
@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 requête = bd.createNamedQuery( ); return requête.getResultList(); } }
Le service web ci-dessus exclut la méthode rechercher()
et demande à renommer la méthode listePersonnels().
.
L'annotation @javax.jws.WebResult fonctionne en relation avec @WebMethod pour contrôler le nom de la valeur renvoyée par le message dans le WSDL.
@WebService @Stateless public class GestionPersonnel { ... @WebMethod(operationName = "listeDuPersonnel") @WebResult(name = "personnels") public List<Personne> listePersonnels() { Query requête = bd.createNamedQuery( ); return requête.getResultList(); } }
Dans le code ci-dessus, le résultat de la méthode listePersonnels()
est renommée personnels.
.
L'annotation @javax.jws.WebParam, dont l'API est présentée ci-dessous, ressemble à @WebResult car elle personnalise les paramètres des méthodes du service web. Cette API permet de modifier le nom du paramètre dans le WSDL, l'espace de nom et le mode de passage des paramètres - IN, OUT, INOUT.
@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 ; public String partName() default ; }
@WebService @Stateless public class GestionPersonnel { ... @WebMethod public void nouveau(@WebParam(name = "agent") Personne personne) { bd.persist(personne); } ... }
L'annotation @OneWay peut être utilisée avec les méthodes qui ne renvoient aucun résultat, comme celles de type void. Cette annotation ne possède aucun élément et peut être considérée comme une interface de marquage informant le conteneur que l'appel de cette méthode peut être optimisé (en utilisant un appel asynchrone, par exemple) puisqu'il n'y a pas de valeur de retour.
@WebService @Stateless public class GestionPersonnel { ... @WebMethod @OneWay @Asynchronous public void nouveau(@WebParam(name = "agent") Personne personne) { bd.persist(personne); } ... }
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 mémorisent pas l'état, soit ils n'existent pas, soit ils sont prêts à traiter une requête. Ce cycle de vie est géré par le conteneur.
Comme elles s'exécutent dans un conteneur, les EJB
autorisent l'injection de dépendances et les méthodes de rappel du cycle
de vie : si elle existe, le conteneur appelera la méthode de rappel
@PostConstruct lorsqu'il crée une instance d'un service web et la
méthode de rappel @PreDestroy lorsqu'il la
détruit.
Un service web possède un contexte d'environnement auquel il peut accéder en injectant une référence à javax.xml.ws.WebServiceContext au moyen de l'annotation @Resource. Dans ce contexte, le service peut obtenir des informations d'exécution comme la classe qui implémente l'extrémité, le contexte du message et des informations concernant la sécurité relative à la requête qui est traitée.
@WebService @Stateless public class GestionPersonnel { @Resource private WebServiceContext contexte; ... }
Méthode | Description |
---|---|
getMessageContext() | Renvoie le MessageContext de la requête en cours de traitement au moment de l'appel. Permet d'accéder aux en-têtes du message SOAP, etc. |
getUserPrincipal() | Renvoie le principal qui identifie l'émetteur de la requête en cours de traitement. |
isUserInRole() | Teste si l'auteur authentifié appartient au rôle logique indiqué. |
getEndPointReference() | Renvoie l'EndPointReference associé à cette extrémité. |
Vous pouvez invoquer un service web en utilisant le fichier WSDL et certains outils de génération 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 méthode local pour invoquer un service se trouvant sur un autre hôte. La différence est que le service web sur l'hôte 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 appelées SEI (service endpoint interfaces) car ce sont des représentations Java d'une extrémité 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 méthode de ce proxy
est appelé, elle convertit ses paramètres en message SOAP
(la requête) et l'envoie à
l'extrémité du web service. Pour obtenir le résultat, la réponse SOAP
est reconvertie en une instance du type renvoyé. Pour l'utiliser, il n'est
pas nécessaire de connaître le fonctionnement interne du proxy
ni d'étudier son code. Avant de compiler le consommateur client, il est
nécessaire de produire la SEI afin d'obtenir
la classe proxy pour l'appeler dans le code.
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-même. Vous devez ensuite obtenir la classe proxy GestionPersonnel afin de permettre localement l'invocation des méthodes métiers. Nous appelons localement les méthodes nouveau(), modifier(), etc. du proxy, qui, à leurs tours, invoquerons le service web distant, créeront les requêtes SOAP, sérialiserons les messages, etc.
Pour que cette injection fonctionne, ce code doit s'exécuter 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 impérativement par la programmation.
Les classes produites par l'outil wsimport peuvent être directement utilisées. Il suffit alors tout simplement de créer une instance de GestionPersonnelService à l'aide de l'opérateur new ; le reste est entièrement identique. Du coup, l'injection peut ne pas paraître très intéressante si ce n'est le fait d'avoir l'application cliente automatiquement installée au travers de Java Web Start.
Les outils wsimport et wsgen sont founis avec le JDK 1.6. Vous pouvez accéder directement à ces outils ou passer par l'interface en ligne de commande de Glassfish.
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()) ... }
Le développement et l'utilisation d'un service web comporte quatre phases :
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 deuxième, qui sera traité dans un chapitre à part entière permettra de gérer l'identité du personnel d'une entreprise. Ce premier projet d'archivage englobe en réalité deux parties :
Procédons par ordre et commençons par créer le service web qui va être capable d'archiver les photos désirées sur le disque dur du serveur. Le point d'extrémité est un bean session. Du coup, il est nécessaire de prévoir un projet de type "Module EJB". Le nom du projet s'intitule ArchiverPhotos qui possède un bean session Archiver, de type stateless, dont voici le code :
package session; import java.io.*; import javax.ejb.*; import javax.jws.*; @WebService @Stateless public class Archiver { private final String répertoire = ; @Asynchronous public void stocker(String nom, byte[] octets) throws IOException { File fichier = new File(répertoire+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(répertoire+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(répertoire).list(); } public void supprimer(String nom) { new File(répertoire+nom).delete(); } }
Ce code est finalement extrêmement simple puisque toute la problématique réseau est totalement occultée 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 représentant le bean session.
Remarquez au passage que la méthode stocker() est asynchrone, ce qui permet de libérer l'application cliente dès que l'envoi des octets est réalisé. Chacun reprend la main de son côté, et le serveur peut ainsi, pendant ce temps là, créer le fichier image correspondant dans le répertoire de stockage dédié.
Une fois que ces quelques lignes sont introduites, dans
netbeans pas exemple, vous pouvez demander l'exécution de ce service (un
simple clic sur le bouton Run) qui dans
l'ordre propose : la compilation et l'archivage suivie du déploiement
sur le serveur d'applications Glassfish. Ce dernier génère alors
effectivement le service web avec les différents artefacts nécessaires
(à 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 spécialisées qui s'occupent uniquement, soit de générer des documents XML en relation avec la réponse du service souhaité, soit d'être capable de remettre en forme une requête sous forme d'appel de méthode (avec les arguments nécessaires) à 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 proposée par défaut, et donc à l'aide d'un simple
navigateur. Nous retrouvons ainsi notre module EJB ArchiverPhotos
à l'intérieur duquel nous découvrons notre bean session Archiver.
Il est alors possible de consulter par la suite le document WSDL
définissant le service web.
Réaliser un service web avec Netbeans et Glassfish est vraiment très simple et très rapide. Tout se fait en coulisse. Après avoir mis l'annotation @WebService sur un bean session sans état (seul possibilité), il suffit ensuite de demander l'exécution pour que le déploiement et la mise en service totale se réalise automatiquement, et ceci dans les plus brefs délais.
Nous pouvons dès lors nous consacrer à l'application cliente qui va utiliser ce service web. Il s'agit d'une simple application fenêtrée. L'avantage, c'est que cette application peut très bien être utilisée depuis Internet puisque le protocole utilisé est le protocole HTTP sur le port 80 (ici 8080) qui est généralement 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 première chose à s'occuper est de générer les artefacts côté client (à l'aide de wsimport) afin que nous disposions de toutes les classes nécessaires au dialogue avec le service web au travers du protocole SOAP/XML. Ces classes, je le rappelle, vont s'occuper de traduire ou de générer des documents XML.
Ceci dit, encore une fois, il ne sera pas
nécessaire de manipuler directement wsimport.
Netbeans le fait à votre place. Voici la procédure à suivre. Il suffit
de faire appel au menu WebServiceClient et de
spécifier alors la localisation du document WSDL
afin que les artefacts soient en adéquation avec le service web utilisé.
Dès que vous validez votre choix, wsimport
est automatiquement exécuté et génère donc à votre place l'ensemble des
classes nécessaires à la communication avec le web service au travers du
protocole SOAP / XML.
Maintenant tout est prêt, nous pouvons donc écrire notre codage Java correspondant à toute la partie graphique afin de permettre la sélection des photos présentes 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 décrire.
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 sélecteur = new JFileChooser(); private File fichier; private byte[] octets; private boolean effacer = true; private static Archiver archivage; // <----------------------------------------------------------------------------------------------------------------- accès au service web public Client() { super( ); add(onglets); onglets.add( , panneauLocal); onglets.add( , panneauServeur); panneauLocal.add(outilsLocal, BorderLayout.NORTH); panneauLocal.add(new JScrollPane(photoLocale)); outilsLocal.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { sélecteur.setFileSelectionMode(JFileChooser.FILES_ONLY); if (sélecteur.showOpenDialog(Client.this)==JFileChooser.APPROVE_OPTION) { fichier = sélecteur.getSelectedFile(); photoLocale.setIcon(new ImageIcon(fichier.getPath())); } } }); outilsLocal.add(new AbstractAction( ) { 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 méthode stocker() du service web listingPhotos(); } catch (Exception ex) { setTitle( ); } } }); panneauServeur.add(outilsDistant, BorderLayout.NORTH); panneauServeur.add(new JScrollPane(photoDistante)); panneauServeur.add(description, BorderLayout.SOUTH); outilsDistant.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { sélecteur.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (sélecteur.showSaveDialog(Client.this)==JFileChooser.APPROVE_OPTION) { try { fichier = new File(sélecteur.getSelectedFile() + +(String)listePhotos.getSelectedItem()); FileOutputStream fluxImage = new FileOutputStream(fichier); fluxImage.write(octets); fluxImage.close(); } catch (Exception ex) { setTitle( ); } } } }); outilsDistant.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { archivage.supprimer((String)listePhotos.getSelectedItem()); // <------------------------ appel de la méthode 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 méthode restituer() du service web ByteArrayInputStream fluxImage = new ByteArrayInputStream(octets); photoDistante.setIcon(new ImageIcon(ImageIO.read(fluxImage))); } catch (Exception ex) { setTitle( ); } } }); 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 méthode 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(); } }
Nous venons de le dire, l'utilisation d'un service web est vraiment très simple et très intuitive. Il suffit d'appeler la méthode qui vous intéresse de l'objet distant (service web) en spécifiant les bonnes valeurs dans les arguments requis. Par contre, la communication entre le client et le service web se fait par l'intermédiaire de documents XML formatés suivant le protocole SOAP.
Les artefacts sont donc là pour se préoccuper
de la formation et de l'analyse de documents XML. Nous allons détailler
plus précisément l'architecture de ces artefacts.
Il est à noter que JAXB utilise également les méthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de sérialisation, toutes les méthodes get sont sollicitées pour générer l'élement XML correspondant. Dans la désérialisation, c'est l'inverse, l'objet est d'abord créé, et ensuite toutes les méthodes set sont sollicitées pour compléter définitivement l'objet à partir du document XML et des éléments correspondants.
Visualisons à ce sujet, côté client,
l'interface Archiver représentant le
service web. Notez que le nom des arguments n'est pas celui
d'origine. Remarquez également que le retour de la méthode liste()
n'est plus de type String[], mais une List<String> :
Nous allons maintenant conclure et bien visualiser au travers d'un cas d'utilisation, l'enchaînement des communications entre le client et le service web au travers du tube HTTP en utilisant le protocole SOAP / XML.
Rappelez-vous que JAXB utilise également les méthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de sérialisation, toutes les méthodes get sont sollicitées pour générer l'élement XML correspondant. Dans la désérialisation, c'est l'inverse, l'objet est d'abord créé, et ensuite toutes les méthodes set sont sollicitées pour compléter définitivement l'objet à partir du document XML et des éléments correspondants.
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 présents actuellement sur le serveur :
Je vous propose de réaliser un deuxième projet qui permet de gérer le personnel d'une entreprise, ce qui va nous permettre de voir comment transiter une entité au travers du réseau, tout en respectant le protocole SOAP / XML. Par contre, nous fairons très peu de description, uniquement ce qui s'avèrera nécessaire.
Ce deuxième projet de gestion de personnel, comme
le projet précédent, englobe également deux parties :
Procédons par ordre et commençons par nous occuper du serveur. Il est nécessaire de prévoir un projet de type "Module EJB". Le nom du projet s'intitule tout simplement Personnels, et possède deux éléments importants, d'une part, le bean session sans état GestionPersonnel qui intègre le service web et qui gère la persistance, et l'entité Personne, qui se préoccupe de rendre persistant chaque personnel de l'entreprise. Commençons justement par cette dernière :
package entité; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQuery(name= , query= ) 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; } }
Un objet Personne est échangé entre le consommateur et le service web. Lorsque nous avons décrit l'architecture d'un service web, nous avons vu que les données échangées devaient être des documents XML : nous avons donc besoin d'une méthode 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 réciproquement.
Rappelez-vous que JAXB utilise également les méthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de sérialisation, toutes les méthodes get sont sollicitées pour générer l'élement XML correspondant. Dans la désérialisation, c'est l'inverse, l'objet est d'abord créé, et ensuite toutes les méthodes set sont sollicitées pour compléter définitivement l'objet à partir du document XML et des éléments correspondants.
Cette remarque est importante puisque les méthodes setNom() et setPrenom() réalisent pas mal de traitement avant de renseigner les attributs correspondants. Ainsi, lorsque le service web récupére le document XML correspondant au personnel, le nom et le prénom de cette personne sont automatiquement formatés comme nous le désirons, juste au moment de la phase de désérialisation.
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 requête = bd.createNamedQuery( ); return requête.getResultList(); } }
Le service est maintenant opérationnel, du moins si vous avez pensé à compiler votre module et à le déployer sur le serveur Glassfish, tout ceci en l'exécutant 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 première chose à s'occuper est de générer les artefacts côté client (à l'aide de wsimport) afin que nous disposions de toutes les classes nécessaires au dialogue avec le service web au travers du protocole SOAP/XML. Ces classes vont s'occuper de traduire ou de générer des documents XML.
Ceci dit, encore une fois, il ne sera pas nécessaire de manipuler directement wsimport. Netbeans le fait à votre place. Vous connaissez maintenant la procédure à suivre. Il suffit de faire appel au menu WebServiceClient et de spécifier alors la localisation du document WSDL afin que les artefacts soient en adéquation avec le service web utilisé.
Dès que vous validez votre choix, wsimport
est automatiquement exécuté et génère donc à votre place l'ensemble des
classes nécessaires à la communication avec le web service au travers du
protocole SOAP / XML.
package ws; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = , propOrder = { , , , , }) 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; } }
Rappelez-vous que JAXB utilise également les méthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de sérialisation, toutes les méthodes get sont sollicitées pour générer l'élement XML correspondant. Dans la désérialisation, c'est l'inverse, l'objet est d'abord créé, et ensuite toutes les méthodes set sont sollicitées pour compléter définitivement l'objet à partir du document XML et des éléments correspondants.
Il est possible de prévoir des méthodes supplémentaires, 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 nécessaires comme montré ci-dessous.
package ws; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = , propOrder = { , , , , }) 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; } }
Maintenant tout est prêt, nous pouvons donc écrire notre codage Java correspondant à toute la partie graphique afin de permettre la gestion complète du personnel de l'entreprise à distance (même sur Internet) et de mémoriser ainsi toutes les actions proposées sur la base de données du serveur et ceci, au travers du service web.
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 prénom = new JTextField(); private JFormattedTextField âge = new JFormattedTextField(0); private JComboBox téléphones = 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( ); add(outils, BorderLayout.NORTH); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { personne = new Personne(nom.getText(), prénom.getText(), (Integer)âge.getValue()); for (int i=0; i<téléphones.getItemCount(); i++) personne.getTelephones().add((String) téléphones.getItemAt(i)); gestion.nouveau(personne); listingPersonnes(); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { String téléphone = (String) téléphones.getSelectedItem(); téléphones.addItem(téléphone); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { personne.modifier(nom.getText(), prénom.getText(), (Integer)âge.getValue()); gestion.modifier(personne); listingPersonnes(); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { gestion.supprimer(personne); listingPersonnes(); } }); outils.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { personne = new Personne(); rafraîchir(); } }); panneau.setLayout(new GridLayout(4, 2)); panneau.add(new JLabel( )); panneau.add(nom); panneau.add(new JLabel( )); panneau.add(prénom); panneau.add(new JLabel( )); panneau.add(âge); panneau.add(new JLabel( )); panneau.add(téléphones); téléphones.setEditable(true); add(panneau); add(liste, BorderLayout.SOUTH); liste.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) { personne = (Personne) liste.getSelectedItem(); rafraîchir(); } } }); 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 rafraîchir() { nom.setText(personne.getNom()); prénom.setText(personne.getPrenom()); âge.setValue(personne.getAge()); téléphones.removeAllItems(); for (String téléphone : personne.getTelephones()) téléphones.addItem(téléphone); } public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } }