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 5 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é.
L'interopérabilité s'appuie sur l'utilisation de standards techniques définis par le W3C :
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 :
Le terme SOA - Service Oriented Architecture définit une architecture logicielle à base de services. Un service désigne une action exécutée par un composant fournisseur à l'attention d'un composant consommateur.
Ces sigles distinguent le commerce inter-entreprise (Business to Business ou B2B) du commerce avec les particuliers (Business to Consumer ou B2C). Il y a aussi les échanges entreprises-administrations (Business to Administration ou B2A) et ceux entre particuliers comme les enchères, petites annonces, etc. (Consumer to Consumer ou C2C).
URL (Uniform Resource Locator) est une chaîne de caractères servant à localiser des resources consultable à l'aide d'un navigateur Les URL sont un sous-ensemble d'URI (Unified Resource Identifier), qui est un mode d'adressage élaboré de ressources (utilisé notamment pour le Web. Plus précisément, l'URI est un élément générique qui se décline en trois sous-ensembles :
- L'URN (Uniform Resource Name) qui permet un nommage unique et permanent (même si la ressource devient inaccessible).
- L'URC (Uniform Resource Characteristic) qui décrit les caractéristiques de la ressource
- L'URL qui donne sa localisation.
XML-RPC (Remote Procedure Call) est un protocole qui a été conçu pour permettre à des structures de données complexes d'être transmises, exécutées et renvoyées très facilement sur des plate-formes hétérogènes. XML-RPC est l'ancêtre de SOAP.
Un annuaire UDDI est constitué de pages blanches (nom de l'entreprise, adresse, contacts), jaunes (secteurs d'affaires relatifs au service web) et vertes (informations techniques des services web proposés).
Le World Wide Web Consortium (W3C) est un consortium promouvant la compatibilité des technologies web telles que HTML, XHTML, XML, RDF, CSS, SOAP, WSDL, UDDI, etc.
Le proxy, très utilisé pour la gestion d'objet distribués, ajoute un niveau de redirection vers une méthode d'un objet. L'idée est de construire un Proxy capable de communiquer avec un objet distant sans que l'appelant fasse de différence entre un accès local ou un accès distant.
JAX-RPC (Java API for XML-based Remote Procedure Call) est une API permettant de créer des services clients web basés sur XML et RPC.
Souvent décrit comme le nouveau modèle de référence pour les systèmes d'information, les services web permettent à des applications de dialoguer à distance, et ceci indépendamment des plate-formes et des langages sur lesquels elles reposent. Pour communiquer, les services web s'appuient sur les standards que nous venons d'évoquer. Cette communication est basée sur le principe de demandes et de réponses (en fait, des messages XML) transportés par le protocole HTTP.
Les services web sont aujourd'hui incontournables et se présentent comme le nouveau paradigme des architectures logicielles ou architectures orientées services (SOA). Cette technologie tend à s'imposer comme le nouveau standard en termes d'intégration et d'échanges B2B. Grâce aux services web, les applications peuvent être vues comme un ensemble de services métier, structurés et dialoguant selon un standard, plutôt qu'un ensemble d'objets et de méthodes.
Les services web s'appuient sur un ensemble d'API et de protocoles standardisant l'invocation de composants applicatifs. Ils sont décrit en XML par des documents WSDL qui précisent les méthodes pouvant être invoquées, leurs signatures et les points d'accès du service (URL, port). UDDI est le standard de recherche de ces services et SOAP le protocole utilisé pour échanger ces informations.
Java EE 5 a considérablement simplifié le développement des services web. Nous n'aurons donc pas à manipuler directement tous ces standards. Il est cependant important d'en connaître la définition et leur rôle dans l'architecture globale des services web.
Inspiré de XML-RPC, SOAP (Simple Object Access Protocol) est le protocole utilisé par les services web. Fondé sur XML, ce protocole autorise l'interopérabilité avec différents environnements logiciels quelle que soit leur plate-forme d'exécution (comme DCOM ou Corba IIOP). En effet, SOAP permet 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 :
Après avoir présenté le protocole SOAP, décrivons plus précisément sa manipulation. En premier lieu, il est important de noter qu'en pratique SOAP ne se manipule pas directement. En effet, en Java EE 5, l'API utilisée pour échanger des messages est JAX-WS (Java API for XML-based Web Services). Cela nous permet donc de nous concentrer sur le développement et l'invocation de services web, sans se soucier du protocole SOAP.
Voici juste un petit exemple simple pour comprendre le fonctionnement. Soit l'interface suivante :
public interface AgenceDeVoyage { public void faireRéservation(int idBâteau, int numéroCabine, double prix); }
Voici le document SOAP qui est envoyé par le protocole HTTP pour une réservation particulière :
<?xml version='1.0' encoding='UFT-8'> <env:Enveloppe xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:agence ="http://portable:8080/AgenceDeVoyage/" /> <env:Body> <agence:faireRéservation> <idBâteau>23</idBâteau> <numéroCabine>9395</numéroCabine> <prix>5677.56</prix> </agence:faireRéservation> </env:Body> </env:Enveloppe>
UDDI (Universal Description, Discovery and Integration) est la spécification définissant la manière de publier et de retrouver des services web. C'est un annuaire qui offre des mécanismes d'enregistrement et de recherche de services web développés et publiés par des entreprises. UDDI fournit des informations sur l'auteur de services web (adresse, contact...), sur la classification (société informatique, hôpital, ...) et sur les moyens techniques permettant de les invoquer. Chaque entreprise peut avoir son propre annuaire UDDI pour publier ses services, amis elle peut aussi utiliser des annuaires publics comme ceux de Microsoft, IMB ou SAP.
UDDI n'est pas fondamentale aux services web comme l'est XML, SOAP ou WSDL. Nous n'aborderons donc pas la publication de services dans cette étude.
WSDL (Web Service Description Language) est le format XML spécifié par le W3C permettant de définir un service web qui utilise le protocole SOAP. Nous exposons ainsi au format XML la signature d'un service web accessible sur Internet. Cette signature inclut les opérations exposées, le type de ses paramètres d'entrées-sorties, et l'adresse réseau à laquelle nous pourrons l'invoquer.
UDDI permet de retrouver un service web, et WSDL de décrire ses méthodes.
.
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.
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-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é (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.
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.
Dans l'exemple ci-dessous, une classe Java utilise des annotations JAX-WS qui vont permettre par la suite de générer le document WSDL. Le document WSDL est auto-générer par le serveur d'application au moment du déploiement :
@WebService() public class StockerPhotos { @WebMethod public void stocker(String nomFichier, Byte [] octets) { ... } ... }
JAX-WS s'appuie sur l'API JAXB 2.0 pour tout ce qui concerne la correspondance entre document XML et objets Java. Un graphe d'objets est constitué suivant les éléments constituant le document XML. JAXB (Java Architecture for XML Binding) facilite la correspondance bidirectionnelle en fournissant un niveau d'abstraction plus élevé que SAX ou DOM et en s'appuyant sur les annotations.
Par exemple, si nous désirons obtenir une représentation XML de la classe Client, il suffit de l'annoter avec @javax.xml.bind.annotation.XmlRootElement. D'autres annotations permettent de spécifier qu'un attribut est un identifiant avec @XmlID ou de renommer un attribut (e-mail au lieu de email, par exemple) avec @XmlAttribute :
@XmlRootElement public class Client { @XmlID private String id; private String nom; private String prénom; @XmlAttribute(name="e-mail") private String email; }
Ces annotations permettent alors de générer le document XML suivant à partir de la classe, et inversement :
<?xml version="1.0" encoding="UTF-8"?> <client> <id>3456</id> <nom>REMY</nom> <prénom>Emmanuel</prénom> <e-mail>emmanuel.remy@orange.fr</e-mail> </client>
Le document WSDL qui est généré par le serveur d'applications respecte le standard XML Schema. Ce standard permet de définir précisément les types de chaque élément constituant le document XML. Ces types sont important puiqu'ils définissent les paramètres de chaque méthode de la classe qui constitue le service web. Ainsi le mapping entre une classe Java est son correspond XML peut se faire correctement grâce à JAXB en respectant les types proposés.
Vous avez justement ci-dessous la correspondance entre les types XML Schema et les types Java :
| XML Schema | Types Java |
|---|---|
| xsd:byte | Byte |
| xsd:Boolean | Boolean |
| xsd:short | Short |
| xsd:int | Integer |
| xsd:long | Long |
| xsd:float | Float |
| xsd:double | Double |
| xsd:string | java.lang.String |
| xsd:dateTime | java.util.Calendar |
| xsd:integer | java.math.BigInteger |
| xsd:decimal | java.math.BigDecimal |
| xsd:QName | java.xml.namespace.QName |
| xsd:base64Binary | Byte [ ] |
| xsd:hexBinary | Byte [ ] |
Vous remarquez que le mapping proposé par le couple JAX-WS/JAXB utilise systématiquement les classes enveloppes plutôt que les types primitifs. Vous pouvez malgré tout utiliser les types primitifs pour les paramètres de vos méthodes puisque l'auto-boxing entre la classe enveloppe et son type primitif se réalise automatiquement, sauf pour les tableaux d'octets. En effet, un Byte[] est totalement différent d'un byte[].
Généralement, la définition de tous les types utilisés par le service web, notamment par les paramètres des méthodes accessibles sont placés dans un fichier à part et qui correspond justement au Schema prévu. Ainsi, nous aurons donc deux fichiers, le fichier WSDL qui porte l'extension <*.wsdl> qui fera lui même référence au fichier XML Schema dont l'extension est généralement <*.xsd>.
En réalité, le développement d'un service web est relativement simple bien que plusieurs technologies soient mises en oeuvre. Telle est la force de Java EE. Pour cela, il existe plusieurs phases de génération de code qui entre en jeu et qui mettent en oeuvre toute la tuyauterie technique. Le développement et l'utilisation d'un service web comporte quatre phases :
Un artefact est composé de l'ensemble des documents nécessaires à un service web. Nous pouvons citer par exemple le document WSDL ou encore les classes Java qui formeront les messages SOAP d'échanges XML.
La première chose à faire consiste à créer notre service web. Il s'agit tout simplement d'une classe qui sera déployée dans le serveur d'applications. Il existe deux moyen d'implémenter un service web. Le premier repose sur les servlets où une simple classe annotée @WebService est déployée dans un conteneur Web (dans un <.war>). L'autre moyen repose sur les EJB sans état qui sont annotées à la fois @Stateless et @WebService, puis déployés dans un conteneur EJB (dans un <.jar> ou <.ear>).
Afin d'illustrer mes propos, nous allons reprendre le projet de l'étude précédente. Nous avions effectivement mis en oeuvre un système qui permet d'archiver des photos numériques. Nous sommes passé par un bean session qui offre toutes méthodes nécessaires et dont le code est présenté ci-dessous. Nous allons reprendre ce bean session pour qu'il devienne un service web.
| photos.ArchivagePhotosBean.java |
|---|
package photos; ... @Stateless public class ArchivagePhotosBean implements ArchivagePhotosRemote { public void stocker(String intitulé, byte[] octets) throws IOException { File fichier = new File(répertoire + intitulé); FileOutputStream stockage = new FileOutputStream(fichier); stockage.write(octets); stockage.close(); } public String[] liste() { return new File(répertoire).list(); } public void supprimer(String nom) { File fichier = new File(répertoire+nom); fichier.delete(); } public byte[] getPhoto(String nom) throws IOException { File fichier = new File(répertoire+nom); byte[] octets = new byte[(int)fichier.length()]; FileInputStream photo = new FileInputStream(fichier); photo.read(octets); photo.close(); return octets; } } |
Comme nous l'avons découvert dans le chapitre précédent, les annotations JAX-WS sont spécifiques aux services web. Elles permettent d'agir sur la structure d'un document WSDL en modifiant certains paramètres du service ou des méthodes qui le compose. Dans cette rubrique, nous allons décrire plus précisément le comportement de ces annotations.
L'annotation principale pour définir un service web est @javax.jws.WebService. Cette annotation s'applique à une classe et utilise éventuellement plusieurs attributs qui permettent, par exemple, de spécifier la localisation d'un service web.
package photos; import javax.jws.*; import javax.ejb.Stateless; @WebService() @Stateless public class StockerPhotos { ... }
Voici La liste des attributs qu'il est possible de mettre en oeuvre :
package photos; import javax.jws.*; import javax.ejb.Stateless; @WebService public interface StockerPhotosEndPoint { ... } @WebService(endpointInterface = "StockerPhotosEndPoint") @Stateless public class StockerPhotos implements StockerPhotosEndPoint { ... }
Cette annotation peut être utilisée sur une classe (comme dans notre exemple) ou sur l'interface métier dite "endpoint". Dans le deuxième cas, il faut impérativement indiquer l'interface utilisée via l'attribut endpointInterface dans la classe d'implémentation.
Si une classe est annotée par @WebService, alors par défaut, toutes ces méthodes publiques peuvent être appelées. Si vous souhaitez restreindre cette règle et ne publier que certaines méthodes, nous pouvons alors utiliser l'annotation @javax.jws.WebMethod. Celle-ci permet aussi de modifier certains attributs par défaut.
package photos; import javax.jws.*; import javax.ejb.Stateless; @WebService() @Stateless() public class StockerPhotos { @WebMethod(operationName = "stocker") @Oneway public void stocker(String nomFichier, Byte [] octets) { ... } ... }
Cette annotation @WebMethod est utilisée pour déclarer les méthodes accessibles par le service web (nous parlons d'opération dans le protocole SOAP). Voici les détails des attributs qu'elle contient :
Une méthode qui n'a pas de paramètre de retour peut être annotée par @Oneway.
.
Les paramètres de la méthode ainsi que la valeur de retour peuvent aussi être changées. L'annotation @javax.jws.WebParam permet de contrôler la génération du WSDL qui concerne les paramètres de la méthode.
package photos; import javax.jws.*; import javax.ejb.Stateless; @WebService() @Stateless() public class StockerPhotos { @WebMethod(operationName = "stocker") @Oneway public void stocker(@WebParam(name = "nomFichier") String nomFichier, @WebParam(name = "octets") Byte [] octets) { ... } ... }
Ainsi, l'annotation @WebParam permet de préciser des informations concernant les arguments des opérations définies dans le fichier WSDL. Voici le détail des attributs disponibles :
L'annotation @WebResult est presque identique, mais elle annote la valeur de retour de la méthode.
.
Voici le résultat obtenu en tenant compte de ces différentes annotations :
| photos.StockerPhotos |
|---|
package photos; import java.io.*; import javax.jws.Oneway; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebService; import javax.ejb.Stateless; @WebService() @Stateless() public class StockerPhotos { private final String répertoire = "J:/Photos/"; @WebMethod(operationName = "stocker") @Oneway public void stocker(@WebParam(name = "nomFichier") String nomFichier, @WebParam(name = "octets") Byte [] octets) { try { File fichier = new File(répertoire + nomFichier); FileOutputStream stockage = new FileOutputStream(fichier); byte[] bytes = new byte[octets.length]; for (int i = 0; i < bytes.length; i++) bytes[i] = octets[i]; stockage.write(bytes); stockage.close(); } catch (IOException ex) { System.err.println("Problème de stockage"); } } @WebMethod(operationName = "liste") public java.lang.String [] liste() { return new File(répertoire).list(); } @WebMethod(operationName = "supprimer") @Oneway public void supprimer(@WebParam(name = "nomFichier") String nomFichier) { File fichier = new File(répertoire+nomFichier); fichier.delete(); } @WebMethod(operationName = "getPhoto") public Byte [] getPhoto(@WebParam(name = "nomFichier") String nomFichier) { try { File fichier = new File(répertoire + nomFichier); byte[] octets = new byte[(int) fichier.length()]; FileInputStream photo = new java.io.FileInputStream(fichier); photo.read(octets); photo.close(); java.lang.Byte[] bytes = new java.lang.Byte[octets.length]; for (int i = 0; i < bytes.length; i++) bytes[i] = octets[i]; return bytes; } catch (IOException ex) { System.err.println("Problème de lecture"); return null; } } } |
Voilà, tout est dit. N'est-ce pas magique ? Si les valeurs par défaut vous conviennent, vous pouvez même limiter les annotations à la seule @WebService. Contrairement aux EJB classiques, un service web n'a pas besoin d'implémenter une interface.
Voici donc le même web service en prenant les valeurs par défaut :
| photos.StockerPhotos |
|---|
package photos; import java.io.*; import javax.jws.Oneway; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebService; import javax.ejb.Stateless; @WebService() @Stateless() public class StockerPhotos { private final String répertoire = "J:/Photos/"; public void stocker(String nomFichier, Byte [] octets) { try { File fichier = new File(répertoire + nomFichier); FileOutputStream stockage = new FileOutputStream(fichier); byte[] bytes = new byte[octets.length]; for (int i = 0; i < bytes.length; i++) bytes[i] = octets[i]; stockage.write(bytes); stockage.close(); } catch (IOException ex) { System.err.println("Problème de stockage"); } } public java.lang.String [] liste() { return new File(répertoire).list(); } public void supprimer(String nomFichier) { File fichier = new File(répertoire+nomFichier); fichier.delete(); } public Byte [] getPhoto(String nomFichier) { try { File fichier = new File(répertoire + nomFichier); byte[] octets = new byte[(int) fichier.length()]; FileInputStream photo = new java.io.FileInputStream(fichier); photo.read(octets); photo.close(); java.lang.Byte[] bytes = new java.lang.Byte[octets.length]; for (int i = 0; i < bytes.length; i++) bytes[i] = octets[i]; return bytes; } catch (IOException ex) { System.err.println("Problème de lecture"); return null; } } } |
Nous avons passé beaucoup de temps à expliquer pas mal de chose mais en réalité vous remarquez que nous n'avons pas grand chose à faire. Dans l'absolu, il suffit juste de placer l'annotation @WebService sur la classe bu bean désirée et ensuite de la déployer sur le serveur d'applications et tout s'exécute automatiquement. Effectivement, c'est assez magique !
Cette classe développée, StockerPhotos doit générer les artefacts de son service, c'est-à-dire le document WSDL et les classes Java qui formeront les messages d'échanges XML suivant le protocole SOAP. Tout est auto-généré par le serveur d'applications.
En réalité, le serveur d'applications utilise un utilitaire pour cela qui dans le cas de GlassFish se nomme wsgen. Nous verrons comment faire à travers la plate-forme de développement NetBeans dans le chapitre suivant.
A partir de la classe StockerPhotos, l'utilitaire wsgen génère les éléments suivants :
| StockerPhotos.wsdl |
|---|
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://photos/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://photos/" name="StockerPhotosService"> <types> <xsd:schema> <xsd:import namespace="http://photos/" schemaLocation="http://portable:8080/StockerPhotosService/StockerPhotos?xsd=1"> </xsd:import> </xsd:schema> </types> <message name="stocker"> <part name="parameters" element="tns:stocker"></part> </message> <message name="liste"> <part name="parameters" element="tns:liste"></part> </message> <message name="listeResponse"> <part name="parameters" element="tns:listeResponse"></part> </message> <message name="supprimer"> <part name="parameters" element="tns:supprimer"></part> </message> <message name="getPhoto"> <part name="parameters" element="tns:getPhoto"></part> </message> <message name="getPhotoResponse"> <part name="parameters" element="tns:getPhotoResponse"></part> </message> <portType name="StockerPhotos"> <operation name="stocker"> <input message="tns:stocker"></input> </operation> <operation name="liste"> <input message="tns:liste"></input> <output message="tns:listeResponse"></output> </operation> <operation name="supprimer"> <input message="tns:supprimer"></input> </operation> <operation name="getPhoto"> <input message="tns:getPhoto"></input> <output message="tns:getPhotoResponse"></output> </operation> </portType> <binding name="StockerPhotosPortBinding" type="tns:StockerPhotos"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"></soap:binding> <operation name="stocker"> <soap:operation soapAction=""></soap:operation> <input><soap:body use="literal"></soap:body></input> </operation> <operation name="liste"> <soap:operation soapAction=""></soap:operation> <input><soap:body use="literal"></soap:body></input> <output><soap:body use="literal"></soap:body></output> </operation> <operation name="supprimer"> <soap:operation soapAction=""></soap:operation> <input><soap:body use="literal"></soap:body></input> </operation> <operation name="getPhoto"> <soap:operation soapAction=""></soap:operation> <input><soap:body use="literal"></soap:body></input> <output><soap:body use="literal"></soap:body></output> </operation> </binding> <service name="StockerPhotosService"> <port name="StockerPhotosPort" binding="tns:StockerPhotosPortBinding"> <soap:address location="http://portable:8080/StockerPhotosService/StockerPhotos"></soap:address> </port> </service> </definitions> |
Ce fichier fait ensuite référence à la définition des types qui sont décrit dans le fichier correspondant au XML Schema et qui porte l'extension <.xsd>. Ce fichier est également auto-généré par le serveur d'applications.
| StockerPhotos.xsd |
|---|
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:tns="http://photos/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://photos/"> <xs:element name="getPhoto" type="tns:getPhoto"></xs:element> <xs:element name="getPhotoResponse" type="tns:getPhotoResponse"></xs:element> <xs:element name="liste" type="tns:liste"></xs:element> <xs:element name="listeResponse" type="tns:listeResponse"></xs:element> <xs:element name="stocker" type="tns:stocker"></xs:element> <xs:element name="supprimer" type="tns:supprimer"></xs:element> <xs:complexType name="supprimer"> <xs:sequence> <xs:element name="nomFichier" type="xs:string" minOccurs="0"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="getPhoto"> <xs:sequence> <xs:element name="nomFichier" type="xs:string" minOccurs="0"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="getPhotoResponse"> <xs:sequence> <xs:element name="return" type="xs:byte" nillable="true" minOccurs="0" maxOccurs="unbounded"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="liste"> <xs:sequence></xs:sequence> </xs:complexType> <xs:complexType name="listeResponse"> <xs:sequence> <xs:element name="return" type="xs:string" nillable="true" minOccurs="0" maxOccurs="unbounded"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="stocker"> <xs:sequence> <xs:element name="nomFichier" type="xs:string" minOccurs="0"></xs:element> <xs:element name="octets" type="xs:byte" nillable="true" minOccurs="0" maxOccurs="unbounded"></xs:element> </xs:sequence> </xs:complexType> </xs:schema> |

@XmlRootElement(name="Stocker") public class Stocker { @XmlElement(name="nomFichier") private String nomFichier; @XmlElement(name="octets") private List<Byte> octets; ... }

@XmlRootElement public class ListResponse { @XmlElement private List<String> _return; ... }


StockerPhotos a développé son service web, généré ses artefacts serveurs et a déployé le tout sur son serveur à une URL donnée :
http://portable:8080/StockerPhotosService/StockerPhotos
Cette URL est structurée de la façon suivante :
Maintenant, pour que l'application fenêtrée cliente puisse accéder à ce service, il lui faut générer les artefacts côté client. Cette opération se fait, avec GlassFish, via l'utilitaire wsimport. Celui-ci prend en paramètres l'URL du WSDL du service web :
wsimport http://portable:8080/StockerPhotosService/StockerPhotos?WSDL
Nous verrons comment faire à travers la plate-forme de développement NetBeans dans le chapitre suivant.
.
A partir du WSDL, wsimport génère les éléments suivant :



Une fois le service web déployé et les artefacts clients générés, il est temps de les utiliser pour invoquer le service web depuis l'application cliente fenêtrée photos.EnvoyerPhotos.

Nous reprenons la même interface graphique avec donc le même code que lors de l'étude précédente, en prenant en compte cette fois-ci les modifications nécessaires pour implémenter l'appel au service web.
| photos.EnvoyerPhotos |
|---|
package photos; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import javax.imageio.*; import javax.naming.*; import java.io.*; import java.util.*; public class EnvoyerPhotos extends JFrame implements ActionListener { private String répertoire = "J:/Stockage/"; private static StockerPhotos archivage; private String[] listeLocal; private String[] listeServeur; private Panneau panneauPhoto = new Panneau(); private JComboBox choixLocal; private JComboBox choixServeur; private JButton envoyer = new JButton("Stocker"); private JButton supprimer = new JButton("Supprimer"); private JPanel panneauNord = new JPanel(); private JPanel panneauSud = new JPanel(); public EnvoyerPhotos() throws Exception { listeLocal = new File(répertoire).list(); choixLocal = new JComboBox(listeLocal); java.util.List<String> listeFichiers = archivage.liste(); listeServeur = new String[listeFichiers.size()]; listeFichiers.toArray(listeServeur); choixServeur = new JComboBox(listeServeur); panneauPhoto.change(ImageIO.read(new File(répertoire + choixLocal.getSelectedItem()))); choixLocal.addActionListener(this); envoyer.addActionListener(this); choixServeur.addActionListener(this); supprimer.addActionListener(this); setSize(500, 500); setTitle("Stockage de photos"); panneauNord.add(new JLabel("Photos en local : ")); panneauNord.add(choixLocal); panneauNord.add(envoyer); add(panneauNord, BorderLayout.NORTH); panneauSud.add(new JLabel("Sur le serveur : ")); panneauSud.add(choixServeur); panneauSud.add(supprimer); add(panneauSud, BorderLayout.SOUTH); add(panneauPhoto); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) throws Exception { archivage = new StockerPhotosService().getStockerPhotosPort(); new EnvoyerPhotos(); } public void actionPerformed(ActionEvent e) { if (e.getSource()==choixLocal) { try { panneauPhoto.change(ImageIO.read(new File(répertoire + choixLocal.getSelectedItem()))); } catch (Exception ex) { setTitle("Problème de localisation des photos"); } } else if (e.getSource()==envoyer) { try { File fichier = new File(répertoire+choixLocal.getSelectedItem()); byte[] octets = new byte[(int)fichier.length()]; FileInputStream photo = new FileInputStream(fichier); photo.read(octets); ArrayList<Byte> bytes = new ArrayList<Byte>(); for (byte octet : octets) bytes.add(octet); archivage.stocker((String)choixLocal.getSelectedItem(), bytes); choixServeur.addItem(choixLocal.getSelectedItem()); } catch (Exception ex) { setTitle("Problème avec le fichier"); } } else if (e.getSource()==choixServeur) { try { java.util.List<Byte> photo = archivage.getPhoto((String) choixServeur.getSelectedItem()); Byte[] bytes = new Byte[photo.size()]; photo.toArray(bytes); byte[] octets = new byte[bytes.length]; for (int i=0; i<octets.length; i++) octets[i] = bytes[i]; ByteArrayInputStream fluxImage = new ByteArrayInputStream(octets); panneauPhoto.change(ImageIO.read(fluxImage)); } catch (IOException ex) { setTitle("Problème avec le serveur"); } } else if (e.getSource()==supprimer) { archivage.supprimer((String) choixServeur.getSelectedItem()); choixServeur.removeItem(choixServeur.getSelectedItem()); } } } class Panneau extends JComponent { private BufferedImage image; private double ratio; public void change(BufferedImage image) { if (image!=null) { this.image = image; ratio = (double)image.getWidth()/image.getHeight(); repaint(); } } @Override protected void paintComponent(Graphics surface) { if (image!=null) surface.drawImage(image, 0, 0, this.getWidth(), (int)(this.getWidth()/ratio), null); } } |
Cette fois-ci, c'est beaucoup plus simple à implémenter. En effet, vous n'avez plus besoin de contexte d'initialisation (donc pas besoin de fichier de configuration jndi.properties), ni d'archives propres au serveur d'applications. Vous avez juste besoin de créer un objet de type StockerPhotos qui est l'interface, créée par l'utilitaire wsimport, représentant l'objet distant qui implémente le service web. Cela se fait, nous l'avons vu, par l'intermédiaire de la méthode getStockerPhotosPort() de l'objet StockerPhotosService qui représente réellement le service web. Ensuite, c'est comme pour un bean session, vous avez juste à faire appel aux méthodes de l'interface StockerPhotos pour que le service demandé soit rendu.
Attention, nous devons tenir compte des nouveaux types de valeur et modifier ainsi le codage interne de chacune des méthodes concernées.
.
@WebServiceRef private StockerPhotosService service;
L'inconvénient d'un service web est que le temps de réponse est, semble-t-il, plus long que lors d'un appel à un bean session directement. Par contre, le gros avantage, c'est que nous pouvons appeler ce service n'importe où, soit en réseau local, soit depuis Internet puisque le protocole de transport est le HTTP.
Au niveau codage, que ce soit sur le serveur, ou que ce soit chez le client, le codage d'un service web est extrêmement simple. Toute l'architecture de bas niveau est automatiquement construite par les différents utilitaires proposés par le serveur d'applications. D'ailleurs, avec une plate-forme telle que NetBeans, vous ne vous rendez même pas compte de l'utilisation de ces utilitaires, tout se fait en tâche de fond.

Nous voyons, au travers de cette illustration, l'acheminement des informations lorsque vous désirez visualisée une photo présente sur le serveur :
Nous allons maintenant construire le service web que nous avons étudié dans le chapitre précédent, ainsi que son application cliente, à l'aide de l'environnement intégré NetBeans. Nous allons suivre les deux phases de développement qui va correspondre à la mise en oeuvre de deux projets. Dans un premier temps, nous allons constituer notre service web sur un module EJB. Ensuite, nous allons mettre en place une application classique Java qui utilisera les compétences de ce service web.
La première chose à faire est de construire un nouveau projet de type Entreprise en choisissant la mise en oeuvre d'un module EJB, qui dans notre exemple, se nommera EJBStockerPhotos. Une fois que l'ossature du projet est en place, vous pouvez demander tout de suite la création du service web en faisant un clic bouton droit sur le nom du projet. Un menu contextuel apparaît alors, et vous pouvez donc choisir la rubrique "Web Service...". Il suffit de choisir un nom à votre service web (StockerPhotos) et d'effectuer les réglages nécessaires dans la boîte de dialogue correspondante.

Dans Netbeans, vous trouvez alors deux zones d'éditions différentes :
Lorsque votre code source est correctement complété, vous avez déjà terminé. Vous n'avez effectivement plus rien à faire si ce n'est de lancer le mode "Run", ce qui va permettre de tout mettre en place.
C'est-à-dire :
Voici d'ailleurs se qui s'affiche dans le mode "Output" :

Pour l'application cliente, il suffit de faire un projet Java classique. Une fois que l'ossature du projet est en place, vous pouvez faire appel à un service web au moyen de la rubrique Web Service Client... que vous pouvez atteindre à l'aide du menu contextuel associé au nom du projet. Il suffit alors, grâce à la boîte de dialogue qui apparaît en conséquence, de localiser le fichier WSDL qui définit le service web demandé.

Dès que vous cliquez sur le bouton "Finish" de cette boîte de dialogue, le système recherche effectivement le fichier WSDL correspondant :

L'utilitaire wsimport entre alors en action et fabrique automatiquement toutes les classes nécessaires :

Dès lors, vous n'avez plus qu'à écrire votre code client. C'est vraiment très simple à mettre en oeuvre.
.
Si vous désirez déployer votre application cliente, voici ci-contre l'archive que vous devez construire.
Rremarquez bien la présence de toutes les classes correspondantes aux artefacts côté client.
Cette fois-ci, nous n'avons plus besoin du fichier de propriétés jndi.properties. Par ailleurs, vous n'avez également plus besoin des archives correspondantes au serveur d'applications utilisé.
Pour terminer, il faut dire que le poids de ce fichier archive ClientWebService.jar est vraiment très léger, ce qui favorise le déploiement, à la fois sur le réseau local, mais surtout sur Internet.