Professional Documents
Culture Documents
Le top du top serait de pouvoir faire comme en rseau local, c'est--dire de pouvoir utiliser une application fentre, qui fait appel aux diffrents services, tout en tant sur Internet, et donc sans passer par un navigateur. Il existe une solution pour cela, il s'agit des services web. En ralit, ces services Web communiquent comme une application Web, c'est--dire au travers du protocole HTTP (ce qui permet la communication par Internet).
Au travers de ce protocole HTTP, l'appel des diffrentes mthodes de l'objet distant se fait l'aide d'un document XML, qui est envoy et interprt. Ensuite, le service est rendu par l'envoi galement d'un autre document XML. Ces documents XML respectent un standard (SOAP) propre au service Web. Le gros avantage de cette dmarche, c'est que vous pouvez dvelopper votre service Web avec le langage que vous voulez, de mme que pour l'application cliente. Ainsi par exemple, vous pouvez faire votre service web l'aide de la plate-forme Java EE et dvelopper votre application cliente en .NET. C'est ce que nous appelons : Interoprabilit.
Nous imaginons aisment que toute cette technique est complique. Effectivement elle l'est. Toutefois, grce aux outils de dveloppement comme NetBeans, la fabrication d'un service Web est trs facile mettre en oeuvre. Toute la logique de bas niveau est automatiquement gre. Dans cette tude, nous aborderons ce sujet au travers de cet environnement afin d'viter cette complexit. Il faut dire aussi que Java EE 6 rduit considrablement toute la complexit en s'occupant automatiquement de toute l'ossature de bas niveau. Une autre alternative, malgr qu'elle ne soit pas standard consiste utiliser directement le protocole HTTP sans couche supplmentaire, l'aide cette fois-ci d'un web service dnomm, service web REST. Cette approche est plus simple implmenter. Par ailleurs, les services web, dans ce cas l, sont plutt associs grer des ressources distantes avec toutes les phases classiques, de cration, de rcupration, de modification et de suppression (CRUD). Ces services web REST seront exploits durant le dernier chapitre de cette tude.
Technologie et protocoles
Les services web ncessitent plusieurs technologies et protocoles pour transporter et transformer les donnes d'un client vers un service de faon standard. Les plus courants sont les suivants : UDDI (Universal Description, Discovery and Integration) : est une base de registres et un mcanisme de dcouverte qui ressemble aux pages jaunes. Il sert stocker et classer des services web. UDDI est la spcification dfinissant la manire de publier et de retrouver des services web. C'est un annuaire qui offre des mcanismes d'enregistrement et de recherche de services web dvelopps et publis par des entreprises. UDDI fournit des informations sur l'auteur de services web (adresse, contact...), sur la classification (socit informatique, hpital, ...) et sur les moyens techniques permettant de les invoquer. WSDL (Web Service Description Language) : dfinit l'interface du service web, les donnes et les types des messages, les interactions et les protocoles. Exprim sous forme de document XML, il est utilis pour dcrire : a. Le contrat de services : l'ensemble des oprations disponibles, ainsi que la structure des messages XML changs (exprime en XML Schema). b. Comment, concrtement, transporter les messages XML sur un ou plusieurs protocole (habituellement SOAP). c. La localisation des services. SOAP (Simple Object Access Protocol) : est un protocole d'encodage des messages reposant sur les technologies XML. Il dfinit une enveloppe pour la communication des services web. SOAP permet la transmission de messages entre objets distants, ce qui veut dire qu'il autorise un objet invoquer des mthodes d'objets physiquement situs sur une autre machine. Les messages sont changs l'aide du protocole de transport. Bien que HTTP (Hyper Text Transfert Protocol) soit le plus utilis, d'autres comme SMTP ou JMS sont galement possibles. XML (Extensible Markup Language) est la base sur laquelle sont construits et dfinis les services web (SOAP, WSDL et UDDI). L'intrt des services web rside dans la robustesse du protocole HTTP et donc sa capacit passer plus facilement les pare-feu qu'un autre protocole.
UDDI
Les programmes qui interagissent avec un autre via le Web doivent pouvoir trouver les informations leur permettant de s'interconnecter. UDDI fournit pour cela une approche standardise permettant de trouver les informations sur un service web et sur la faon de l'invoquer. UDDI est une base de registres de services web en XML, un peu comme les professionnels peuvent enregistrer leurs services dans les pages jaunes. Cet enregistrement inclut le type du mtier, sa localisation gographique, le site web, le numro de tlphone, etc. Les autres mtiers peuvent ensuite parcourir cette base et retrouver les informations sur un servcie web spcifique, qui contiennent des mtadonnes supplmentaires dcrivant son comportement et son emplacement. Ces informations sont stockes sous la forme de document WSDL : les clients peuvent lire ce document afin d'obtenir l'information et invoquer
le service.
WSDL
La base de registre UDDI pointe vers un fichier WSDL sur Internet, qui peut tre tlcharg par les consomateurs potentiels. WSDL est un langage de dfinition d'interfaces permettant de dfinir les interactions entre les consomateurs et les services. C'est donc le composant central d'un service web puiqu'il dcrit le type de message, le port, le protocole de communication, les oprations possibles, son emplacement et ce que le client peut en attendre. Vous pouvez considrer WSDL comme une interface Java, mais crite en XML.
Pour garantir l'interoprabilit, l'interface standard du service web doit tre standardise, afin qu'un consomateur et un producteur puissent partager et comprendre un message. C'est le rle de WSDL. SOAP, de son ct, dfinit la faon dont le message sera envoy d'un ordinateur l'autre. En ralit, WSDL est scind en deux parties que nous appelons abstraite et concrte. La signature du service, ses mthodes et ses paramtres sont dcrits de manire abstraite. Cette partie est ensuite lie un protocole de communication et un format de messages concrets. Ainsi, la partie abstraite est totalement dcouple de la manire concrte permettant d'appeler le service. Extrait d'un fichier WSDL du service Web que nous allons mettre en oeuvre : <?xml version="1.0" encoding="UTF-8"?> <definitions targetNamespace="http://photos/" name="StockerPhotosService"> <types> <xsd:schema> <xsd:import namespace="http://photos/" schemaLocation="http://portable:8080/StockerPhotosService/StockerPhotos?xsd=1" /> </xsd:schema> </types> <message name="stocker"> <part name="parameters" element="tns:stocker"></part> </message> ... <service name="StockerPhotosService"> <port name="StockerPhotosPort" binding="tns:StockerPhotosPortBinding"> <soap:address location="http://portable:8080/StockerPhotosService/StockerPhotos"></soap:address> </port> </service> </definitions> Cet extrait de document WSDL commence par l'en-tte <definitions>. Cet lment peut prendre plusieurs attributs facultatifs qui dfinissent des noms de domaines dans la suite du document. Dans notre exemple, la dfinition reoit le nom StockerPhotosService. Le service web portant ce mme nom (<service>) peut tre invoqu partir de l'URL spcifie (http://portable:8080/StockerPhotosService/StockerPhotos) Nous ne nous attarderons pas sur WSDL car, comme vous le dcouvrirez plus loin, ce document est gnr automatiquement et n'a pas tre dvelopp manuellement.
SOAP
SOAP est le protocole standard des services web. Il fournit le mcanisme de communication permettant de connecter les services qui changent des donnes au format XML au moyen du protocole rseau - HTTP le plus souvent. Comme WSDL, SOAP repose fortement sur XML : un message SOAP est un document XML contenant plusieurs lments (une enveloppe, un corps, etc. ). SOAP permet ainsi la transmission de messages entre objets distants, en invoquant des mthodes sur des objets physiquement situs sur une autre machine. Le transfert se fait le plus souvent l'aide du protocole HTTP.
Le protocole SOAP se dcompose en deux parties :
Une enveloppe, contenant des informations sur le message lui-mme afin de permettre son acheminement et son traitement ; Un modle de donnes, dfinissant le format du message, c'est--dire les informations transmettre. Voici deux captures de trames qui visualisent l'appel d'une mthode stocker() avec sa rponse.
JAXB
Les services web envoient des requtes et des rponses en changeant des messages XML. En Java, il existe plusieurs API de bas niveau pour traiter les documents XML et les schmas XML. La spcification JAXB fournit un ensemble d'API et d'annotations pour reprsenter les documents XML comme des artfacts Java reprsentant des documents XML. JAXB facilite la dsrialisation des documents XML en objets et leur srialisation en documents XML. Mme si cette spcification peut tre utilise pour n'importe quel traitement XML, elle est fortement intgre aux services web.
JAX-WS
JAX-WS dfinit un ensemble d'API et d'annotations permettant de construire et de consommer des services web en Java. Elle fournit les outils pour envoyer et recevoir des requtes de services web via SOAP en masquant la complexit du protocole. Ni le consommateur ni le service n'ont donc besoin de produire ou d'analyser des messages SOAP car JAX-WS s'occupe de traitement de bas niveau. JAX-WS dpend lui-mme d'autres spcifications comme JAXB que nous venons de dcouvrir. JAX-WS est la nouvelle appellation de JAX-RPC (Java API for XML Based RPC) qui permet de dvelopper trs simplement des services web. JAX-WS fournit un ensemble d'annotations pour mapper la correspondance Java-WSDL. Il suffit pour cela d'annoter directement les classes Java qui vont reprsenter le service web. En ce qui concerne le client, JAX-WS permet d'utiliser une classe proxy pour appeler un service distant et masquer la complexit du protocole. Ainsi, ni le client ni le serveur n'ont besoin de gnrer ou de parser les messages SOAP. JAX-WS s'occupe de ces traitements de bas niveau. Dans l'exemple ci-dessous, une classe Java utilise des annotations JAX-WS qui vont permettre par la suite de gnrer le document WSDL. Le document WSDL est auto-gnrer par le serveur d'application au moment du dploiement :
@WebService() public class StockerPhotos { @WebMethod public void stocker(String nomFichier, byte[] octets) { ... } ... }
session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.jws.WebService; import javax.persistence.*; @WebService @Stateless @LocalBean public class GestionPersonnel { @PersistenceContext private EntityManager bd; public void nouveau(Personne personne) { bd.persist(personne); } public Personne rechercher(Personne personne) { return bd.find(Personne.class, personne.getId()); } public void modifier(Personne personne) { bd.merge(personne); } public void supprimer(Personne personne) { bd.remove(rechercher(personne)); } public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Comme les entits ou les EJB, un service web utilise le modle de classe annot avec une politique de configuration par exception. Si tous les choix par dfaut vous conviennent, ceci signifie qu'un service web peut se rduire une simple classe Java annote @javax.ws.WebService. Le service GestionPersonnel propose plusieurs mthodes pour grer l'ensemble du personnel, savoir nouveau(), rechercher(), modifier(), supprimer() et listePersonnels(). Un objet Personne est chang entre le consommateur et le service web. Lorsque nous avons dcrit l'architecture d'un service web, nous avons vu que les donnes changes devaient tre des documents XML : nous avons donc besoin d'une mthode pour transformer un objet Java en XML et c'est l que JAXB entre en jeu avec ses annotations et son API. L'objet Personne doit simplement tre annot par @javax.xml.bind.annotation.XmlRootElement pour que JAXB le transforme en XML et rciproquement.
Grce aux annotations JAXB, il n'est pas ncessaire d'crire de code de bas niveau pour effectuer l'analyse XML car elle se produit en coulisse - le service web et le consomateur manipulent un objet Java. Le consommateur peut tre une classe Java qui cre une instance de Personne puis invoque le service web, comme dans le code suivant :
personnel.Client.java
package personnel; import java.awt.*; import java.awt.event.*; import javax.swing.*; import ws.*; public class Client extends JFrame { private JToolBar outils = new JToolBar(); private JTextField nom = new JTextField(); private JTextField prnom = new JTextField(); private JFormattedTextField ge = new JFormattedTextField(0); private JComboBox tlphones = new JComboBox(); private JPanel panneau = new JPanel(); private JComboBox liste = new JComboBox(); private static GestionPersonnel gestion; private Personne personne = new Personne(); private boolean effacer = true; public Client() { super("Personne"); add(outils, BorderLayout.NORTH); outils.add(new AbstractAction("Enregistrer") { public void actionPerformed(ActionEvent e) { personne = new Personne(nom.getText(), prnom.getText(), (Integer)ge.getValue()); for (int i=0; i<tlphones.getItemCount(); i++) personne.getTelephones().add((String) tlphones.getItemAt(i)); gestion.nouveau(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Ajout tlphone") { public void actionPerformed(ActionEvent e) { String tlphone = (String) tlphones.getSelectedItem(); tlphones.addItem(tlphone); } }); outils.add(new AbstractAction("Modifier") { public void actionPerformed(ActionEvent e) { personne.modifier(nom.getText(), prnom.getText(), (Integer)ge.getValue()); gestion.modifier(personne); listingPersonnes();
} }); outils.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { gestion.supprimer(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Effacer") { public void actionPerformed(ActionEvent e) { personne = new Personne(); rafrachir(); } }); panneau.setLayout(new GridLayout(4, 2)); panneau.add(new JLabel("Nom :")); panneau.add(nom); panneau.add(new JLabel("Prnom :")); panneau.add(prnom); panneau.add(new JLabel("ge :")); panneau.add(ge); panneau.add(new JLabel("Tlphones :")); panneau.add(tlphones); tlphones.setEditable(true); add(panneau); add(liste, BorderLayout.SOUTH); liste.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) { personne = (Personne) liste.getSelectedItem(); rafrachir(); } } }); listingPersonnes(); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void listingPersonnes() { effacer = true; liste.removeAllItems(); effacer = false; for (Personne personne : gestion.listePersonnels()) liste.addItem(personne); } private void rafrachir() { nom.setText(personne.getNom()); prnom.setText(personne.getPrenom()); ge.setValue(personne.getAge()); tlphones.removeAllItems(); for (String tlphone : personne.getTelephones()) tlphones.addItem(tlphone); } public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } } Remarquez que dans la mthode main(), le consommateur n'invoque pas directement le service GestionPersonnel : il utilise une classe GestionPersonnelService et appelle la mthode getGestionPersonnelPort() afin d'obtenir une rfrence (gestion) un GestionPersonnel. Grce elle, il peut ensuite appeler les diffrentes mthodes du service web : nouveau(), modifier(), supprimer() et listePersonnels(). Bien que ce code soit trs simple comprendre, beaucoup de choses se passent en arrire-plan. Pour que tout ceci fonctionne, plusieurs artfacts ont t gnrs automatiquement : un fichier WSDL et des stubs clients qui contiennent toutes les informations pour se connecter l'URL de service web, la srialisation de l'objet Personne en XML, l'appel du service web et la rcupration des diffrents rsultats. La partie visible des services web en Java ne manipule pas directement XML, SOAP ou WSDL. Elle est donc trs simple comprendre. Cependant, certaines parties invisibles sont trs importantes pour l'interoprabilit.
Grce cette simple annotation et un mcanisme de srialisation, JAXB est capable de crer une reprsentation XML d'une instance de Personne.
Liaison
L'API JAXB, dfinie dans le paquetage javax.xml.bind, fournit un ensemble d'interfaces et de classes permettant de produire des documents XML et des classes Java - en d'autres termes, elle relie les deux modles. Le framework d'excution de JAXB implmente les oprations de srialisation et de dsrialisation. 1. La srialisation (ou marshalling) consiste convertir les instances des classes annotes par JAXB en reprsentations XML. 2. Inversement, la dsrialisation (unmarshalling) consiste convertir une reprsentation XML en arborescence d'objets.
Les donnes XML srialises peuvent tre valides par un schma XML - JAXB peut produire automatiquement ce schma partir d'un ensemble de classes et vice versa. JAXB fournit galement un compilateur de schmas (xjc) et un gnrateur de schma (schemagen) - alors que la srialisation/dsrialisation manipule des objets et des documents XML, ce compilateur et ce gnrateur de schmas manipulent des classes et des schmas XML.
Annotation
Par bien des aspects, JAXB ressemble JPA. Cependant, au lieu de faire correspondre les objets une base de donnes, JAXB les lie un document XML. Comme JPA, JAXB dfinit un certain nombre d'annotations (dans la paquetage javax.xml.bind.annotation) afin de configurer cette association et s'appuie sur la configuration par exception pour allger le travail du dveloppeur.
Comme le montre le code suivant, l'quivalent de @Entity des objets persistants est l'annotation @XmlRootElement de JAXB :
A l'aide du marshalling, nous obtenons aisment une reprsentation XML d'un objet de type Personne. L'lment racine <personne> reprsente l'objet Personne et inclut la valeur de chaque attribut de la classe.
Associer une classe ou une numration un element XML. Trs souvent utilis pour spcifier la racine du document XML. Associer un espace de nommage un paquetage. Associer un type Java ou une numeration un type dfini dans un schma. Conteneur pour plusieurs proprits annotes par XmlSchemaType. Marquer une entit pour qu'elle ne soit pas mappe dans le document XML. Convertir une classe ou une numration vers le type spcifi dans Shma XML correspondant. Mapper une classe vers le type complexe dans le Schma XML ou vers le type simple suivant le cas.
WSDL
Les documents WSDL sont hbergs dans le conteneur de service web et utilisent XML pour dcrire ce que fait un service, comment appeler ses oprations et o le trouver. Ce document XML respecte une structure bien tablie, forme de plusieurs parties. Le sercice GestionPersonnel, par exemple, utilise les lments suivants : <definition> : est l'lment racine de WSDL. Il prcise les dclarations des espaces de noms visibles dans tout le document. <types> : dfinit les types de donnes utiliss par les messages. Ici, c'est la dfinition du schma XML (GestionPersonnel?xsd=1) qui dcrit le type des paramtres de la requte adresse au service (un objet GestionPersonnel) et le type de la rponse (List<Personne>). <message> : dfinit le format des donnes changes entre le consommateur du service web et le service web lui-mme. Ici, il s'agit de la requte (la mthode listePersonnels) et de la rponse (listePersonnelsResponse). <portType> : prcise les oprations du service (la mthode nouveau par exemple). <binding> : dcrit le protocole concret (SOAP, ici) et les formats des donnes pour les oprations et les messages dfinis pour un type de port particulier. <service> : contient une collection d'lments <port> associs, chacun une extrmit (une adresse rseau ou une URL).
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="modifier"> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_modifier_WSAT_Policy" /> <soap:operation soapAction="" /> <input> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_modifier_WSAT_Policy" /> <soap:body use="literal" /> </input> <output> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_modifier_WSAT_Policy" /> <soap:body use="literal" /> </output> </operation> <operation name="nouveau"> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_nouveau_WSAT_Policy" /> <soap:operation soapAction="" /> <input> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_nouveau_WSAT_Policy" /> <soap:body use="literal" /> </input> <output> <wsp:PolicyReference URI="#GestionPersonnelPortBinding_nouveau_WSAT_Policy" /> <soap:body use="literal" /> </output> </operation> </binding> <service name="GestionPersonnelService"> <port name="GestionPersonnelPort" binding="tns:GestionPersonnelPortBinding"> <soap:address location="http://214-0:8080/GestionPersonnelService/GestionPersonnel" /> </port> </service> </definitions> L'lment <xsd:import namespace> fait rfrence un schma XML qui doit tre disponible sur le rseau pour les clients du WSDL. .
SOAP
Alors que WSDL dcrit une interface abstraite du service web, SOAP fournit une implmentation concrte en dfinissant la structure XML des messages changs. Dans le cadre du Web, SOAP est une structure de messages pouvant tre dlivrs par HTTP (ou d'autres protocoles de communication) - la liaison HTTP de SOAP contient quelques en-ttes d'extension HTTP standard. Cette structure de message est dcrite en XML. Au lieu d'utiliser HTTP pour demander une page web partir d'un navigateur, SOAP envoie un message XML via une requte HTTP et reoit une rponse HTTP. Un message SOAP est un document XML contenant les lments suivants : <Enveloppe> : Dfinit le message et l'espace de noms utiliss dans le document. Il s'agit de l'lment racine obligatoire. <Header> : Contient les attributs facultatifs du message ou l'infrastructure spcifique l'application, comme les informations sur la scurit ou le routage rseau. <Body> : Contient le message chang entre les applications. <Fault> : Fournit des informations sur les erreurs qui surviennent au cours du traitement du message. Cet lment est facultatif. Seuls l'enveloppe et le corps sont obligatoires. Dans notre exemple, une application cliente appelle le service web pour connatre l'ensemble du personnel de l'entreprise (une enveloppe SOAP pour la requte) et reoit la liste des agents (une autre enveloppe SOAP pour la rponse).
Le modle JAX-WS
Comme la plupart des composants de Java EE 6, les services web s'appuient sur le paradigme de la configuration par exception. Seule l'annotation @WebService est ncessaire pour transformer une simple classe en service web, mais cette classe doit respecter les rgles suivantes : Elle doit tre annote par @javax.jws.WebService. Pour transformer un service web en lment EJB, la classe doit tre annote par @javax.ejb.Stateless. Elle doit tre publique et ne doit pas tre finale ni abstraite. Elle doit possder un constructeur par dfaut public. Elle ne doit pas dfinir la mthode finalize(). Un service doit tre un objet sans tat et ne pas mmoriser l'tat spcifique d'un client entre les appels de mthode. Au niveau du service, les systmes sont dfinis en terme de messages XML, d'oprations WSDL et de messages SOAP. Cependant, au niveau Java, les applications sont dcrites en termes d'objets, d'interfaces et de mthodes. Il est donc ncessaire d'effectuer une traduction des objets Java vers les oprations WSDL. L'environnement d'excution de JAXB utilise les annotations pour savoir comment srialiser/dsrialiser une classe vers/ partir de XML. Gnralement ces annotations sont caches au dveloppeur du service web. De mme, JAX-WS se sert d'annotations pour dterminer comment srialiser un appel de mthode vers un message de requte SOAP et comment dsrialiser une rponse SOAP vers une instance du type du rsultat de la mthode. Il existe deux sortes d'annotations : Les annotations de traduction WSDL : Elles appartiennent au paquetage javax.jws et permettent de modifier les associations entre WSDL et Java. Les annotations @WebMethod, @WebResult, @WebParam et @OneWay sont utilises sur le service web pour adapter la signature des mthodes exposes. Les annotations de traduction SOAP : Elles appartiennent au paquetage javax.jws.soap et permettent d'adapter la liaison SOAP (@SOAPBinding et @SOAPMessageHandler).
@WebService
L'annotation @javax.jws.WebService marque une classe ou une interface Java comme tant un service web. Cette annotation possde un certain nombre d'attributs permettant de personnaliser le nom du services web dans le fichier WSDL (lments <wsdl:portType> ou <wsdl:service>) et son espace de noms ainsi que l'emplacement du fichier WSDL lui-mme (attribut wsdlLocation).
@WebMethod
Par dfaut, toutes les mthodes publiques d'un service web sont exposes dans le WSDL et utilisent toutes les rgles d'association par dfaut. L'annotation @javax.jws.WebMethod permet de personnaliser certaines de ces associations de mthodes. Son API est assez simple et permet de renommer une mthode ou de l'exclure du WSDL.
session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd; public void nouveau(Personne personne) { bd.persist(personne); } @WebMethod(exclude = true) public Personne rechercher(Personne personne) { return bd.find(Personne.class, personne.getId()); } public void modifier(Personne personne) { bd.merge(personne); } public void supprimer(Personne personne) { bd.remove(rechercher(personne)); } @WebMethod(operationName = "listeDuPersonnel") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Le service web ci-dessus exclut la mthode rechercher() et demande renommer la mthode listePersonnels(). .
@WebResult
L'annotation @javax.jws.WebResult fonctionne en relation avec @WebMethod pour contrler le nom de la valeur renvoye par le message dans le WSDL.
session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { ... @WebMethod(operationName = "listeDuPersonnel") @WebResult(name = "personnels") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Dans le code ci-dessus, le rsultat de la mthode listePersonnels() est renomme personnels. .
@WebParam
L'annotation @javax.jws.WebParam, dont l'API est prsente ci-dessous, ressemble @WebResult car elle personnalise les paramtres des mthodes du service web. Cette API permet de modifier le nom du paramtre dans le WSDL, l'espace de nom et le mode de passage des paramtres - IN, OUT, INOUT.
public boolean header() default false; public String partName() default ""; }
session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { ... @WebMethod public void nouveau(@WebParam(name = "agent") Personne personne) { bd.persist(personne); } ... }
@OneWay
L'annotation @OneWay peut tre utilise avec les mthodes qui ne renvoient aucun rsultat, comme celles de type void. Cette annotation ne possde aucun lment et peut tre considre comme une interface de marquage informant le conteneur que l'appel de cette mthode peut tre optimis (en utilisant un appel asynchrone, par exemple) puisqu'il n'y a pas de valeur de retour.
session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { ... @WebMethod @OneWay @Asynchronous public void nouveau(@WebParam(name = "agent") Personne personne) { bd.persist(personne); } ... }
session.GestionPersonnel.java
@WebService @Stateless public class GestionPersonnel { @Resource private WebServiceContext contexte; ... }
Mthode Description
Renvoie le MessageContext de la requte en cours de traitement au moment de l'appel. Permet d'accder aux en-ttes du message SOAP, etc. Renvoie le principal qui identifie l'metteur de la requte en cours de traitement. Teste si l'auteur authentifi appartient au rle logique indiqu. Renvoie l'EndPointReference associ cette extrmit.
Le consommateur peut obtenir une instance du proxy par injection ou en la crant par programme. Nous injectons un client de service web l'aide de l'annotation @javax.xml.ws.WebServiceRef, qui ressemble aux annotations @Resource ou @EJB prsents dans les tudes prcdentes. Lorsqu'elle est applique un attribut, le conteneur injecte une instance du service web au moment o l'application est initialise.
personnel.Client.java
package personnel; ... public class Client extends JFrame { @WebServiceRef private static GestionPersonnelService service; private static GestionPersonnel gestion; ... public static void main(String[] args) { gestion = service.getGestionPersonnelPort(); new Client(); } ... gestion.nouveau(personne); gestion.modifier(personne); gestion.supprimer(personne); gestion.listePersonnels()) ... } La classe GestionPersonnelService est la SEI, pas le service web lui-mme. Vous devez ensuite obtenir la classe proxy GestionPersonnel afin de permettre localement l'invocation des mthodes mtiers. Nous appelons localement les mthodes nouveau(), modifier(), etc. du proxy, qui, leurs tours, invoquerons le service web distant, creront les requtes SOAP, srialiserons les messages, etc. Pour que cette injection fonctionne, ce code doit s'excuter dans un conteneur (de servlet, d'EJB ou de client d'application) : dans le cas contraire, nous ne pouvons pas utiliser l'annotation @WebServiceRef et nous devons alors passer imprativement par la programmation. Les classes produites par l'outil wsimport peuvent tre directement utilises. Il suffit alors tout simplement de crer une instance de GestionPersonnelService l'aide de l'oprateur new ; le reste est entirement identique. Du coup, l'injection peut ne pas paratre trs intressante si ce n'est le fait d'avoir l'application cliente automatiquement installe au travers de Java Web Start. Les outils wsimport et wsgen sont founis avec le JDK 1.6. Vous pouvez accder directement ces outils ou passer par l'interface en ligne de commande de Glassfish. wsimport prend en entre un fichier WSDL et produit les artefacts JAX-WS, une SEI notamment. wsgen lit une classe extrmit de service web et produit le WSDL.
personnel.Client.java
package personnel; ... public class Client extends JFrame { private static GestionPersonnel gestion; ... public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } ... gestion.nouveau(personne); gestion.modifier(personne); gestion.supprimer(personne); gestion.listePersonnels()) ... }
Conclusion
Le dveloppement et l'utilisation d'un service web comporte quatre phases : Dveloppement du service web proprement dit. Gnration des artefacts ct serveur (wsgen). Gnration des artefacts ct client (wsimport). Appel du service web chez le client.
Finalement l'ensemble de ce projet global va tre constitu de deux sous projets correspondant deux postes informatiques diffrents, c'est--dire deux machines virtuelles galement diffrentes.
Diagramme de dploiement
session.Archiver.java
package session; import java.io.*; import javax.ejb.*; import javax.jws.*; @WebService @Stateless public class Archiver { private final String rpertoire = "C:/Archivage/"; @Asynchronous public void stocker(String nom, byte[] octets) throws IOException { File fichier = new File(rpertoire+nom); if (fichier.exists()) return; FileOutputStream photo = new FileOutputStream(fichier); photo.write(octets); photo.close();
} public byte[] restituer(String nom) throws IOException { File fichier = new File(rpertoire+nom); if (!fichier.exists()) return null; FileInputStream photo = new FileInputStream(fichier); byte[] octets = new byte[(int)fichier.length()]; photo.read(octets); photo.close(); return octets; } public String[] liste() { return new File(rpertoire).list(); } public void supprimer(String nom) { new File(rpertoire+nom).delete(); } } Ce code est finalement extrmement simple puisque toute la problmatique rseau est totalement occulte par le fait que nous utilisons un bean session (objet distant). Par ailleurs, la mise en oeuvre du service web est encore plus simple puisqu'il suffit juste de proposer la seule annotation @WebService directement sur la classe reprsentant le bean session. Remarquez au passage que la mthode stocker() est asynchrone, ce qui permet de librer l'application cliente ds que l'envoi des octets est ralis. Chacun reprend la main de son ct, et le serveur peut ainsi, pendant ce temps l, crer le fichier image correspondant dans le rpertoire de stockage ddi. Une fois que ces quelques lignes sont introduites, dans netbeans pas exemple, vous pouvez demander l'excution de ce service (un simple clic sur le bouton Run) qui dans l'ordre propose : la compilation et l'archivage suivie du dploiement sur le serveur d'applications Glassfish. Ce dernier gnre alors effectivement le service web avec les diffrents artefacts ncessaires ( l'aide de l'utilitaire interne wsgen) ainsi que le contrat du service sous forme de document WSDL.
Je rappelle que les artefacts sont des classes spcialises qui s'occupent uniquement, soit de gnrer des documents XML en relation avec la rponse du service souhait, soit d'tre capable de remettre en forme une requte sous forme d'appel de mthode (avec les arguments ncessaires) partir d'un document XML envoy par le client. Vous pouvez consulter tout moment notre service web directement sur le serveur Glassfish l'aide de l'application web propose par dfaut, et donc l'aide d'un simple navigateur. Nous retrouvons ainsi notre module EJB ArchiverPhotos l'intrieur duquel nous dcouvrons notre bean session Archiver. Il est alors possible de consulter par la suite le document WSDL dfinissant le service web.
Raliser un service web avec Netbeans et Glassfish est vraiment trs simple et trs rapide. Tout se fait en coulisse. Aprs avoir mis l'annotation @WebService sur un bean session sans tat (seul possibilit), il suffit ensuite de demander l'excution pour que le dploiement et la mise en service totale se ralise automatiquement, et ceci dans les plus brefs dlais.
Ds que vous validez votre choix, wsimport est automatiquement excut et gnre donc votre place l'ensemble des classes ncessaires la communication avec le web service au travers du protocole SOAP / XML.
Maintenant tout est prt, nous pouvons donc crire notre codage Java correspondant toute la partie graphique afin de permettre la slection des photos prsentes sur le disque dur local et de les envoyer par la suite sur le disque dur du serveur par le service web que nous venons de dcrire.
photos.Client.java
package photos; import java.awt.BorderLayout; import java.awt.event.*; import java.io.*; import javax.imageio.ImageIO; import javax.swing.*; import ws.*; public class Client extends JFrame { private JTabbedPane onglets = new JTabbedPane(); private JPanel panneauLocal = new JPanel(new BorderLayout()); private JPanel panneauServeur = new JPanel(new BorderLayout()); private JToolBar outilsLocal = new JToolBar(); private JToolBar outilsDistant = new JToolBar(); private JLabel photoLocale = new JLabel(); private JLabel photoDistante = new JLabel(); private JLabel description = new JLabel(); private JComboBox listePhotos = new JComboBox(); private JFileChooser slecteur = new JFileChooser(); private File fichier; private byte[] octets; private boolean effacer = true; private static Archiver archivage; // <----------------------------------------------------------------------------------------------------------------- accs au service web public Client() { super("Envoyer des photos"); add(onglets); onglets.add("Photos en local", panneauLocal); onglets.add("Photos distantes", panneauServeur); panneauLocal.add(outilsLocal, BorderLayout.NORTH); panneauLocal.add(new JScrollPane(photoLocale)); outilsLocal.add(new AbstractAction("Slectionner") { public void actionPerformed(ActionEvent e) { slecteur.setFileSelectionMode(JFileChooser.FILES_ONLY); if (slecteur.showOpenDialog(Client.this)==JFileChooser.APPROVE_OPTION) { fichier = slecteur.getSelectedFile(); photoLocale.setIcon(new ImageIcon(fichier.getPath())); } } }); outilsLocal.add(new AbstractAction("Envoyer") { public void actionPerformed(ActionEvent e) { if (fichier!=null) try { byte[] octets = new byte[(int) fichier.length()]; FileInputStream lecture = new FileInputStream(fichier); lecture.read(octets); lecture.close(); archivage.stocker(fichier.getName(), octets); // <------------------------------------------ appel de la mthode stocker() du service web listingPhotos();
} catch (Exception ex) { setTitle("Impossible d'envoyer le fichier"); } } }); panneauServeur.add(outilsDistant, BorderLayout.NORTH); panneauServeur.add(new JScrollPane(photoDistante)); panneauServeur.add(description, BorderLayout.SOUTH); outilsDistant.add(new AbstractAction("Restituer") { public void actionPerformed(ActionEvent e) { slecteur.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (slecteur.showSaveDialog(Client.this)==JFileChooser.APPROVE_OPTION) { try { fichier = new File(slecteur.getSelectedFile() + "/" +(String)listePhotos.getSelectedItem()); FileOutputStream fluxImage = new FileOutputStream(fichier); fluxImage.write(octets); fluxImage.close(); } catch (Exception ex) { setTitle("Problme pour restituer la photo en local"); } } } }); outilsDistant.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { archivage.supprimer((String)listePhotos.getSelectedItem()); // <------------------------ appel de la mthode supprimer() du service web listingPhotos(); } }); outilsDistant.add(listePhotos); listePhotos.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) try { octets = archivage.restituer((String) listePhotos.getSelectedItem()); // <------- appel de la mthode restituer() du service web ByteArrayInputStream fluxImage = new ByteArrayInputStream(octets); photoDistante.setIcon(new ImageIcon(ImageIO.read(fluxImage))); } catch (Exception ex) { setTitle("Problme pour rcuprer l'image du serveur"); } } }); listingPhotos(); setSize(500, 400); setLocationByPlatform(true); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void listingPhotos() { effacer = true; listePhotos.removeAllItems(); for (String photo : archivage.liste()) listePhotos.addItem(photo); // <----------------------------------- appel de la mthode liste() du service web effacer = false; if (listePhotos.getItemCount()>0) listePhotos.setSelectedIndex(0); } public static void main(String[] args) { archivage = new ArchiverService().getArchiverPort(); new Client(); } }
L'lment important est, bien entendu, l'interface Archiver. Elle reprsente l'objet distant qui ralise l'ensemble du service dsir. Il suffit juste de faire appel aux bonnes mthodes - stocker(), restituer(), supprimer() et liste() - de l'objet local relatif cette interface, qui joue le rle de proxy, pour obtenir la rponse aux requtes dsires.
Pour obtenir ce proxy, objet implmentant l'interface Archiver, nous devons passer par une instance de la classe ArchiverService et faire appel la mthode getArchiverPort(). Une fois que l'objet archivage reprsentant cette classe Archiver est cr, pour le client, l'utilisation du service est vraiment trs simple d'emploi. Il suffit de faire appel la mthode dsire. Encore une fois, toute la problmatique rseau est totalement occult. Il faut noter que ce proxy implmente bien entendu toutes les mthodes prvues par le service web, mais possde galement toutes les mthodes utiles supplmentaires pour communiquer correctement avec le service web, en sous-marin, en utilisant les classes artefacts qui utilise les annotations JAXB. Ainsi, lorsque le client fait appel une mthode spcifique du service web, le proxy cre une instance de la classe artefact correspondante et ralise ainsi automatiquement le mapping Objet/XML qui va tre utile pour le protocole SOAP. Effectivement, ce sont les artefacts qui se proccupent d'envoyer les requtes encapsules dans un document XML sous une enveloppe SOAP et au travers du tube HTTP. La rponse la requte est galement un document XML qui est renvoy par le serveur, toujours en respectant le protocole SOAP. Ce document XML est ensuite transcrit (mapping Objet/XML) afin de rcuprer la rponse suivant le format attendu.
Pour chaque mthode du service web, nous disposons systmatiquement de deux artefacts, c'est--dire de deux classes, un pour la requte correspondant l'appel de la mthode, et un autre pour la valeur de retour de cette mthode. Le nom des classes artefacts correspond au nom de la mthode du service web. La premire classe correspondant la requte porte excatement le nom de la mthode avec toutefois, la premire lettre en majuscule. La deuxime classe est similaire la premire mais dispose en plus du suffixe Response. Ainsi donc, notre service web, propose quatre mthodes, respectivement : stocker(), restituer(), liste() et supprimer(). Nous avons donc notre dispositions les artefacts suivants : Stocker, StockerResponse, Restituer, RestituerResponse, Liste, ListeResponse et Supprimer, SupprimerResponse.
A chaque argument d'une mthode du service web correspond une proprit (un attribut, une mthode get et une mthode set) dans la classe artefact. Nous pourrions nous demander quoi servent ces proprits (avec donc systmatiquement un get et un set). Il faut bien comprendre que chaque artefact existe la fois ct client, mais galement ct serveur. Ainsi, lorsque nous voquons la mthode set d'un artefact d'un ct, de l'autre ct c'est la mthode get du mme artefact qui est invoqu, et vice versa.
Tous ces artefacts sont l pour raliser automatiquement des mapping Objet / XML. Dans le cas de la srialisation, l'application cliente par exemple cre une instance d'un artefact, par exemple Stocker, remplie les proprits avec les valeurs requises au moyen des mthode set. Ensuite, au moyen de JAXB, cet objet est transcrit en document XML correspondant. Le service web, grce au mme artefact, va raliser l'opration inverse, la dsrialisation, c'est--dire, toujours au moyen de JAXB, dcripter ce document XML envoy par le client au travers du rseau, et gnrer ainsi un objet, par exemple de type Stocker, qui est finalement une image parfaite de l'objet qui se trouve sur le client, et au moyen des mthodes get, cette fois-ci, le service web collecte les paramtres de la mthode du service web solliciter. Il est noter que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Lors de l'laboration du service web, nous avons plac la seule annotation @WebService, sans rajouter les annotations @WebMethod, @WebParam, etc. La consquence, c'est que le nom des proprits est un nom par dfaut. Ainsi, nous retrouverons systmatiquement les proprits arg0, arg1, etc. Visualisons ce sujet, ct client, l'interface Archiver reprsentant le service web. Notez que le nom des arguments n'est pas celui d'origine. Remarquez galement que le retour de la mthode liste() n'est plus de type String[], mais une List<String> :
Nous allons prendre le temps de bien dmontrer la communication entre les deux machines, l'aide d'un analyseur de trame, en faisant l'tude d'une mthode particulire du service web.
Pour archiver une photo dans le serveur, il est bien sr ncessaire, au pralable, de choisir la photo sur le poste local et de charger le fichier image correspondant en mmoire centrale. A partir de cette image, il est ensuite possible de collecter son nom de fichier et tout le contenu de la photo sous forme de tableau d'octets. Nous disposons maintenant de tous les lments ncessaires pour communiquer directement avec la mthode stocker() du service web (appel d'une mthode d'un objet distant, mme au travers d'Internet), ce qui est trs simple raliser. Toutefois, en sous-marin, avant d'arriver au service web, nous utilisons le proxy archivage qui implmente effectivement toutes les mthodes du service web, ct client, mais il est galement capable de gnrer des documents XML, respectant ainsi le protocole SOAP, qui vont tre envoy au travers du tube HTTP. Ainsi, lorsque nous faisons appel la mthode stocker() du proxy archivage, ce dernier gnre une partie du document XML envoyer simplement au moyen de la classe artefact Stocker. Cet artefact Stocker utilise effectivement les annotations JAXB. Dans un premier temps, le proxy cre un objet de cette classe, renseigne ensuite les attributs correspondants aux paramtres de la mthode stocker(), par les mthodes set, setArg0() et setArg1(). Une fois que cet objet est compltement renseign, le proxy, l'aide de JAXB, ralise ainsi un mapping Objet / XML (srialisation). Rappelez-vous que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Cet lment XML gnr, puisqu'il a t produit par la classe Stocker, rprsente ainsi la mthode stocker() avec ses deux paramtres, le nom du fichier d'une part et son contenu sous forme de tableau d'octets. Pour que le document dans son ensemble soit complet, Il faut proposer autour de cet lment, l'enveloppe SOAP correspondante, toujours en format XML, bien entendu. Une fois que le document XML est complet, il est enfin envoy au serveur sur le tube HTTP.
C'est le service web lui-mme qui collecte ce document, qui le dcripte tout de suite pour savoir quelle est la mthode qui est demande. C'est effectivement l'enveloppe qui fournit cette information. Puisque maintenant, la mthode du service est connue, il suffit de rcuprer la structure XML correspondante l'intrieur de cette enveloppe, et de solliciter JAXB, avec de nouveau la classe annote Stocker (ct serveur), mais en faisant l'opration inverse cette fois-ci, la dsrialisation, qui consiste faire un mapping XML / Objet. Grce JAXB, nous retrouvons l'objet complet reprsentant cette classe Stocker, avec donc le nom du fichier et bien sr tout son contenu sous forme de tableau d'octet. Ainsi, nous possdons exactement les mmes valeurs dans la classe Stocker du client et dans la classe Stocker du serveur. L'un est image de l'autre. Il ne reste plus qu' rcuprer chacune des valeurs importantes, savoir le nom du fichier et le tableau d'octets, au moyen respectivement des mthodes getArg0() et getArg1() de cette classe Stocker afin de permettre l'appel la mthode stocker() du service web. L aussi, nous retrouvons le mme appel de mthode stocker(), au niveau du proxy ct client, et bien sr au niveau du service web lui-mme. L'une est image de l'autre.
Par contre, tout le traitement effectif se fait bien entendu ct serveur. Ainsi, le fichier reprsentant la photo est finalement gnr dans le disque dur du serveur. Mme si la mthode stocker() ne possde pas de retour, toute la procdure inverse est propose et va servir d'accus rception. Ainsi, au travers de la classe StockerResponse, un document XML avec son enveloppe SOAP est renvoy au client. A titre d'exemple, je vous propose de voir galement les captures correspondant la demande de suppression d'une photo sur le serveur :
Capture de trames correspondant la demande de liste des fichiers images prsents actuellement sur le serveur :
entit.Personne.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQuery(name="toutLePersonnel", query="SELECT p FROM Personne p ORDER BY p.nom") public class Personne implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String nom; private String prenom; private int age; @ElementCollection(fetch=FetchType.EAGER) private List<String> telephones = new ArrayList<String>(); public long getId() { return id; } public void setId(long id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }
public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom.toUpperCase(); } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { StringBuilder chaine = new StringBuilder(prenom.toLowerCase()); chaine.setCharAt(0, Character.toUpperCase(chaine.charAt(0))); this.prenom = chaine.toString(); } public List<String> getTelephones() { return telephones; } public void setTelephones(List<String> telephones) { this.telephones = telephones; } } Au pralable, il est bien sr ncessaire de crer la base de donnes qui va intgrer toutes les tables issues de cette entit. Cette base de donnes s'intitule personnels. Par ailleurs, vous devez construire l'unit de persistance adquate. Tout cela, nous l'avons dj trait lors de l'tude de la gestion de la persistance, se ralise trs simplement au moyen de Netbeans. Vous pouvez ds lors appliquer cette entit. Remarquez au passage, que le nom et le prnom, lors de leurs mise en place par leurs mthodes set respectives, sont automatiquement mis en forme afin de prendre en compte la casse des caractres, tout en majuscule pour le nom, et la premire lettre en majuscule pour le prnom. Toutefois, le point important dans ce projet est de permettre l'envoi et la rception de cette entit au travers du protocole SOAP / XML. Il est donc galement ncessaire que l'objet relatif cette entit puisse tre automatiquement transforme en document XML et vice versa. Encore une fois, grce la technologie Java, et plus prcisment grce JAXB, il suffit d'une toute petite annotation @XmlRootElement pour que le mapping Objet / XML se ralise automatiquement dans les deux sens. Il n'y a vraiment pas grand chose crire. Mise part cet aspect particulier, le contenu mme de l'entit reste tout fait classique. Un objet Personne est chang entre le consommateur et le service web. Lorsque nous avons dcrit l'architecture d'un service web, nous avons vu que les donnes changes devaient tre des documents XML : nous avons donc besoin d'une mthode pour transformer un objet Java en XML et c'est l que JAXB entre en jeu avec ses annotations et son API. L'objet Personne doit simplement tre annot par @javax.xml.bind.annotation.XmlRootElement pour que JAXB le transforme en XML et rciproquement. Rappelez-vous que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Cette remarque est importante puisque les mthodes setNom() et setPrenom() ralisent pas mal de traitement avant de renseigner les attributs correspondants. Ainsi, lorsque le service web rcupre le document XML correspondant au personnel, le nom et le prnom de cette personne sont automatiquement formats comme nous le dsirons, juste au moment de la phase de dsrialisation.
session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.jws.WebService; import javax.persistence.*; @WebService @Stateless @LocalBean public class GestionPersonnel { @PersistenceContext private EntityManager bd; @Asynchronous public void nouveau(Personne personne) { bd.persist(personne); } private Personne rechercher(Personne personne) { return bd.find(Personne.class, personne.getId()); } @Asynchronous public void modifier(Personne personne) { bd.merge(personne); } @Asynchronous public void supprimer(Personne personne) { bd.remove(rechercher(personne)); } public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } } Passons maintenant au service web lui-mme. Il s'agit d'un bean session sans tat qui gre la persistance. Le service GestionPersonnel propose plusieurs mthodes pour grer l'ensemble du personnel, savoir nouveau(), modifier(), supprimer() et listePersonnels(). Remarquez que la mthode rechercher() est prive, c'est--dire quelle ne fait pas partie du service web. Elle est, par contre, bien utile pour la mthode supprimer(). Par ailleurs, beaucoup de mthodes de ce service web sont asynchrones, et sont donc annotes @Asynchronous, ce qui permet de librer tout de suite le client afin de grer ensuite en parallle la persistance qui peut prendre un peu de temps (le temps reste quand mme relativement court, c'est juste pour un cas d'cole).
Je le rappelle, nous devons laborer un nouveau projet au travers d'une application Java classique. Avant d'crire le code relatif la mise en oeuvre de l'IHM, la toute premire chose s'occuper est de gnrer les artefacts ct client ( l'aide de wsimport) afin que nous disposions de toutes les classes ncessaires au dialogue avec le service web au travers du protocole SOAP/XML. Ces classes vont s'occuper de traduire ou de gnrer des documents XML. Ceci dit, encore une fois, il ne sera pas ncessaire de manipuler directement wsimport. Netbeans le fait votre place. Vous connaissez maintenant la procdure suivre. Il suffit de faire appel au menu WebServiceClient et de spcifier alors la localisation du document WSDL afin que les artefacts soient en adquation avec le service web utilis. Ds que vous validez votre choix, wsimport est automatiquement excut et gnre donc votre place l'ensemble des classes ncessaires la communication avec le web service au travers du protocole SOAP / XML.
Remarquez bien que la mthode rechercher() ne fait effectivement pas parti des mthodes du web service. Par ailleurs, la classe Personne est automatiquement gnre galement ct client par l'outil wsimport puisque la plupart des mthodes l'utilisent, soit en paramtre, soit en valeur de retour. N'oubliez pas que nous avons galement bien pris soin de placer l'annotation @XmlRootElement sur l'entit Personne, ct serveur. Si nous ouvrons le fichier de cette classe Personne ct client, et si nous supprimons tous les commentaires javadoc, voici ce que nous obtenons :
ws.Personne.java
package ws; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "personne", propOrder = { "age", "id", "nom", "prenom", "telephones" }) public class Personne { protected int age; protected long id; protected String nom; protected String prenom; @XmlElement(nillable = true) protected List<String> telephones; public int getAge() { return age; }
public void setAge(int value) { this.age = value; } public long getId() { return id; } public void setId(long value) { this.id = value; } public String getNom() { return nom; } public void setNom(String value) { this.nom = value; } public String getPrenom() { return prenom; } public void setPrenom(String value) { this.prenom = value; } public List<String> getTelephones() { if (telephones == null) { telephones = new ArrayList<String>(); } return this.telephones; } } Nous pouvons faire un certain nombre de remarques. Cette classe Personne, ct client est diffrente de l'originale, l'entit Personne sur le serveur. Seuls les attributs et les mthodes set et get sont ncessaires ce niveau. Je tiens souligner qu'il est trs importants que ces mthodes get et set soient prsentes dans l'entit, sinon, au moment de la gnration de code automatique avec l'utilitaire wsimport, nous ne les retrouverions pas ct client. Cette remarque est primordiale, ct client, les mthodes set sont utilises, nous l'avons vu, par le proxy. Ensuite, lorsque l'objet est compltement renseign, JAXB, en interne, utilise de son ct plutt les mthodes get. Les deux types de mthode sont donc indispensables si vous dsirez exprimer compltement l'objet transfrer. Rappelez-vous que JAXB utilise galement les mthodes get et set en interne, sans que cela soit visible. Ainsi, lorsque nous sommes en phase de srialisation, toutes les mthodes get sont sollicites pour gnrer l'lement XML correspondant. Dans la dsrialisation, c'est l'inverse, l'objet est d'abord cr, et ensuite toutes les mthodes set sont sollicites pour complter dfinitivement l'objet partir du document XML et des lments correspondants. Soyez bien attentif galement ce qui est crit ct client. Les mthodes setNom() et setPrenom() n'ont plus du tout le traitement relatif la mise en majuscule. En ralit, ce n'est pas gnant et pas indispensable de ce ct l. Effectivement, pour bien dmontrer ce que je vous voque, je vais vous rappeler le cheminement de la description d'une personne au travers du rseau. Lorsque nous devons, par exemple, envoyer la description complte d'une personne du client vers le serveur. La toute premire chose est, bien entendu, la cration d'un objet Personne ct client. Nous devons ensuite spcifier la valeur des attributs l'aide des mthodes set, moins que nous ayons prvu un constructeur avec plusieurs paramtres qui nous permettrait de remplir dj les attributs concerns. Ensuite, de faon intrinsque, durant la phase de srialisation, le document XML est gnr l'aide cette fois-ci des mthodes get. Le document XML, avec son enveloppe SOAP, est alors envoy jusqu'au service web. Ce dernier, au moyen de JAXB, utilise maintenant les mthodes set de l'entit, donc finalement les mthodes setNom() et setPrenom() originales, pour dsrialiser et produire ainsi l'objet correspondant. C'est juste ce moment l que le nom et le prnom se retrouvent avec les lettres majuscules.
Il est possible de prvoir des mthodes supplmentaires, cot client, sur l'artefact Personne, afin que cela soit plus facile et plus intuitif utiliser par l'IHM. Je vous porpose donc de faire les rajouts ncessaires comme montr ci-dessous.
ws.Personne.java
package ws; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "personne", propOrder = { "age", "id", "nom", "prenom", "telephones" }) public class Personne { protected int age; protected long id; protected String nom; protected String prenom; @XmlElement(nillable = true) protected List<String> telephones; public int getAge() { return age; } public void setAge(int value) { this.age = value; } public long getId() { return id; } public void setId(long value) { this.id = value; } public String getNom() { return nom; } public void setNom(String value) { this.nom = value; } public String getPrenom() { return prenom; } public void setPrenom(String value) { this.prenom = value; } public List<String> getTelephones() { if (telephones == null) { telephones = new ArrayList<String>(); } return this.telephones; } public Personne() { } public Personne(String nom, String prenom, int age) { this.age = age; this.nom = nom;
this.prenom = prenom; } public void modifier(String nom, String prenom, int age) { this.age = age; this.nom = nom; this.prenom = prenom; } @Override public String toString() { return nom+' '+prenom; } } 1. Ainsi, l'artefact original est propos un constructeur o nous spcifions le nom, le prnom et l'ge du personnel. Attention, ds que vous crez un nouveau constructeur, vous tes oblig de prvoir le constructeur par dfaut. 2. J'en profite galement pour rajouter une mthode modifier() et pour redfinir la mthode toString() qui, dans ce dernier cas, me permet de visualiser automatiquement le nom suivi du prnom lorsque nous intgrons un personnel dans la liste droulante de l'IHM cliente. Maintenant tout est prt, nous pouvons donc crire notre codage Java correspondant toute la partie graphique afin de permettre la gestion complte du personnel de l'entreprise distance (mme sur Internet) et de mmoriser ainsi toutes les actions proposes sur la base de donnes du serveur et ceci, au travers du service web.
personnel.Client.java
package personnel; import java.awt.*; import java.awt.event.*; import javax.swing.*; import ws.*; public class Client extends JFrame { private JToolBar outils = new JToolBar(); private JTextField nom = new JTextField(); private JTextField prnom = new JTextField(); private JFormattedTextField ge = new JFormattedTextField(0); private JComboBox tlphones = new JComboBox(); private JPanel panneau = new JPanel(); private JComboBox liste = new JComboBox(); private static GestionPersonnel gestion; private Personne personne = new Personne(); private boolean effacer = true; public Client() { super("Personne"); add(outils, BorderLayout.NORTH); outils.add(new AbstractAction("Enregistrer") { public void actionPerformed(ActionEvent e) { personne = new Personne(nom.getText(), prnom.getText(), (Integer)ge.getValue()); for (int i=0; i<tlphones.getItemCount(); i++) personne.getTelephones().add((String) tlphones.getItemAt(i)); gestion.nouveau(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Ajout tlphone") { public void actionPerformed(ActionEvent e) { String tlphone = (String) tlphones.getSelectedItem(); tlphones.addItem(tlphone); } }); outils.add(new AbstractAction("Modifier") { public void actionPerformed(ActionEvent e) { personne.modifier(nom.getText(), prnom.getText(), (Integer)ge.getValue()); gestion.modifier(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { gestion.supprimer(personne); listingPersonnes(); } }); outils.add(new AbstractAction("Effacer") { public void actionPerformed(ActionEvent e) { personne = new Personne(); rafrachir(); } }); panneau.setLayout(new GridLayout(4, 2)); panneau.add(new JLabel("Nom :")); panneau.add(nom); panneau.add(new JLabel("Prnom :")); panneau.add(prnom); panneau.add(new JLabel("ge :")); panneau.add(ge); panneau.add(new JLabel("Tlphones :")); panneau.add(tlphones); tlphones.setEditable(true); add(panneau); add(liste, BorderLayout.SOUTH); liste.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!effacer) { personne = (Personne) liste.getSelectedItem(); rafrachir(); } } }); listingPersonnes(); pack();
setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void listingPersonnes() { effacer = true; liste.removeAllItems(); effacer = false; for (Personne personne : gestion.listePersonnels()) liste.addItem(personne); } private void rafrachir() { nom.setText(personne.getNom()); prnom.setText(personne.getPrenom()); ge.setValue(personne.getAge()); tlphones.removeAllItems(); for (String tlphone : personne.getTelephones()) tlphones.addItem(tlphone); } public static void main(String[] args) { gestion = new GestionPersonnelService().getGestionPersonnelPort(); new Client(); } }
Les URI devraient tre aussi descriptives que possible et ne dsigner qu'une seule ressource, bien que des URI diffrentes qui identifient des ressources diffrentes puissent mener aux mmes donnes : un instant donn, la liste des photos intressantes publies sur Flickr le 01/01/2012 tait la mmea que la liste des photos donnes au cours des 24 dernires heures, bien que l'information envoye par les deux URI ne ft pas la mme.
Reprsentation Nous pouvons obtenir la reprsentation d'un objet sous forme de texte, de XML de PDF ou tout autre format. Un client traite toujours une ressource au travers de sa reprsentation ; la ressource elle-mme reste sur le serveur. La reprsentation contient toutes les informations utiles propos de l'tat d'une ressource : * http://www.apress.fr/book/catalog/java * http://www.apress.fr/book/catalog/java.csv * http://www.apress.fr/book/catalog/java.xml
La premire URI est la reprsentation par dfaut de la ressource, les reprsentations supplmentaires lui ajoutent simplement l'extension de leur format : .csv, .xml, .pdf, etc. L'autre solution consiste n'exposer qu'une seule URI pour toutes les reprsentation, comme la premire par exemple, et utiliser un mcanisme appel ngociation du contenu, que nous prsenterons un peu plus loin.
Protocole HTTP
La requte la plus simple du protocole HTTP est form de GET suivi d'une URL qui pointe sur des donnes (fichier statiques, traitement dynamique...). Elle est envoye par un navigateur quand nous saisissons directement une URL dans le champ d'adresse du navigateur. Le serveur HTTP rpond en renvoyant les donnes demandes.
En tapant l'URL d'un site, l'internaute envoie (via le navigateur) une requte au serveur. Une connexion s'tablit entre le client et le serveur sur le port 80 (port par dfaut d'un serveur Web). Le navigateur envoie une requte demandant l'affichage d'un document. La requte contient entre autres la mthode (GET, POST, etc.) qui prcise comment l'information est envoye. Le serveur rpond la requte en envoyant une rponse HTTP compose de plusieurs parties, dont : l'tat de la rponse, savoir une ligne de texte qui dcrit le rsultat du serveur (code 200 pour un accord, 400 pour une erreur due au client, 500 pour un erreur due au serveur) ; les donnes afficher.
Une fois la rponse reue par le client, la connexion est ferme. Pour afficher une nouvelle page du site, une nouvelle connexion doit tre tablie.
Requtes et rponses
Un client envoie une requte un serveur afin d'obtenir une rponse. Les messages utiliss pour ces changes sont forms d'une enveloppe et d'un corps galement appel document ou reprsentation. Voici, par exemple, un type de requte envoye un serveur : Cette requte contient plusieurs informations envoyes par le client :
La mthode HTTP GET ; Le chemin, ici la racine "/" ; Plusieurs autre en-ttes de requte. Vous remarquez que la requte n'a pas de corps (un GET n'a jamais de corps). En rponse, le serveur renvoie sa rponse et elle est forme des parties suivantes : Un code de rponse : ici le code est 200 OK. Plusieurs en-ttes de rponse, notamment Date, Server, Content-Type. Ici, le type de contenu est text/html, mais il pourrait s'agir de n'importe quel format comme du XML (application/xml) ou une image (image/jpeg), etc. Un corps ou reprsentation. Ici, il s'agit du contenu de la page web renvoye (qui n'est pas visible sur la figure propose ci-dessus). Pour en savoir plus sur le protocole HTTP et les en-ttes. .
Ngociation du contenu
La ngociation de contenu est dfinie comme "le fait de choisir la meilleure reprsentation pour une rponse donne lorsque plusieurs reprsentations sont disponibles". Les besoins, les souhaits et les capacits des clients varient : la meilleure reprsentation pour l'utilisateur d'un tlphone portable au Japon peut, en effet, ne pas tre la plus adapte un lecteur flux RSS en France.
La ngociation du contenu utilise entre autres les en-ttes HTTP : Accept, Accept-Charset, Accept-Encoding, Accept-Language et User-Agent. Pour obtenir, par exemple, la reprsentation CSV de la liste des livres sur Java publis par Apress, l'application cliente (l'agent utilisateur) demandera http://www.apress.com/books/catalog/java avec un en-tte Accept initialis text/csv. Vous pouvez aussi imaginer que, selon la valeur de l'en-tte Accept-Language, le contenu de ce document CSV pourrait tre traduit par le serveur dans la langue correspondante.
Types de contenu
HTTP utilise des types de supports Intenet (initialement appels types MIME) dans les en-ttes Content-Type et Accept afin de permettre un typage des donnes et une ngociation de contenu ouverts et extensibles. Les types de support Internet sont diviss en cinq catgories : text, image, audio, video et application. Ces types sont leur tour diviss en sous-types (text/plain, text/html, text/xhtml, etc.). Voici quelques-uns des plus utiliss :
text/html HTML est utilis par l'infrastructure d'information du World Wide Web depuis 1990 et sa spcification a t dcrite dans plusieurs documents informels. Le type de support text/html a t initialement dfini en 1995 par le groupe le groupe de traveil IETF HTML. Il permet d'envoyer et d'interprter les pages web classiques. text/plain Il s'agit du type de contenu par dfaut car il est utilis pour les messages textuels simples. imagegif, image/jpeg, image/png Le type de support image exige la prsence d'un dispositif d'affichage (un cran ou une imprimante graphique, par exemple) permettant de visualiser l'information. text/xml, application/xml Envoi et rception de document XML. application/json JSON est un format textuel lger pour l'change de donnes. Il est indpendant des langages de programmation.
Code d'tat
Un code HTTP est associ chaque rponse. La spcification dfinit environ 60 codes d'tats ; l'lment Status-Code est un entier de trois chiffres qui dcrit le contexte d'une rponse et qui est intgr dans l'enveloppe de celle-ci. Le premier chiffre indique l'une des cinq classes de rponses possibles : 1xx : Information : La requte a t reue et le traitement se poursuit. 2xx : Succs : L'action a bien t reue, comprise et accepte. 3xx : Redirection : Une autre action est requise pour que la requte s'effectue. 4xx : Erreur du client : La requte contient des erreurs de syntaxe ou ne peut pas tre excute. 5xx : Erreur du serveur : Le serveur n'a pas russi excuter une requte pourtant apparemment valide.
Voici quelques codes d'tat que vous avez srement dj d rencontrer :
200 OK : La requte a russi. Le corps de l'entit, si elle en possde un, contient la reprsentation de la ressource. 301 Moved Permanently : La ressource demande a t affecte une autre URI permanente et toute rfrence future cette ressource devrait utiliser l'une des URI renvoyes. 404 Not Found : Le serveur n'a rien trouv qui corresponde l'URL demande. 500 Internal Server Error : Le serveur s'est trouv dans une situation inattendue qui l'a empch de rpondre la requte. Pour en savoir plus sur le protocole HTTP et les codes d'erreur. .
JAX-RS 1.1
Pour crire des services web REST, il suffit d'un client et d'un serveur reconnaissant le protocole HTTP. N'importe quel navigateur et un conteneur de servlet HTTP pourraient donc faire l'affaire, au prix d'un peu de configuration XML et d'ajustement du code. Au final, ce code pourrait devenir peu lisible et difficile maintenir : c'est l que JAX-RS vole notre secours. Sa premire version, finalise en octobre 2008, dfinit un ensemble d'API mettant en avant une architecture REST. Au moyen d'annotations, il simplifie l'implmentation de ces services et amliore la productivit. La spcification ne couvre que la partie serveur du REST. Nouveauts de JAX-RS 1.1 : Il s'agit d'une version de maintenance axe sur l'intgration avec Java EE 6 et ses nouvelles fonctionnalits. Les nouveauts principales de JAX-RS 1.1 sont les suivantes : Le support de beans session sans tat comme ressources racine. Il est dsormais possible d'injecter des ressources externes (gestionnaire de persistance, sources de donnes, EJB, etc.) dans une ressource REST. Les annotations JAX-RS peuvent s'appliquer l'interface locale d'un bean ou directement un bean sans interface.
L'approche REST
Comme nous l'avons dj mentionn, REST est un ensemble de contraintes de conceptions gnrales reposant sur HTTP. Ce chapitre s'intressant aux services web et REST drivant du web, nous commencerons par une navigation relle passant en revue les principes du Web. Ce dernier est devenu une source essentielle d'informations et fait dsormais partie de nos outils quotidiens : vous le connaissez donc srement trs bien et cette familiarit vous aidera donc comprendre les concepts et les proprits de REST.
Utiliser POST sur des donnes (au format XML, JSON ou texte) afin de crer une ressource livre avec l'URI http://www.site.com/livres/. Le livre cr, la rponse renvoie l'URI de la nouvelle ressource http://www.site.com/livres/123456. Utiliser GET pour lire la ressource (et les ventuels liens vers d'autres ressources partir du corps de l'entit) l'URI http://www.apress.com/books/123456. Utiliser PUT pour modifier la ressource l'URI http://www.site.com/livres/123456. Utiliser DELETE pour supprimer la ressource l'URI http://www.site.com/livres/123456. En se servant ainsi des verbes HTTP, nous pouvons donc effectuer toutes les actions CRUD (Create, Read, Update, Delete) sur une ressource l'image des bases de donnes.
Sans tat
La dernire fonctionnalit de REST est l'absence d'tat, ce qui signifie que toute requte HTTP est totalement indpendante puisque le serveur ne mmorisera jamais les requtes qui ont t effectues. Pour plus de clart, l'tat de la ressource et celui de l'application sont gnralement diffrencis : l'tat de la ressource doit se trouver sur le serveur et tre partag par tous, tandis que celui de l'application doit rester chez le client et tre sa seule proprit. Si nous revenons l'exemple des livres, l'tat de l'application est que le client a rcupr par exemple une reprsentation du livre dsir, mais le serveur ne mmorisera pas cette information. L'tat de la ressource, quant lui, est l'information sur l'ouvrage : le serveur doit videmment la mmoriser et le client peut le modifier. Si le panier virtuel est une ressource dont l'accs est rserv un seul client, l'application doit stocker l'identifiant de ce panier dans la session du client. L'absence d'tat possde de nombreux avantages, notamment une meilleure adaptation la charge : aucune information de session grer, pas besoin de router les requtes suivantes vers le mme serveur, gestion des erreurs, etc. Si vous devez mmoriser l'tat, le client devra faire un travail supplmentaire pour le stocker.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
rest.Bienvenue.java
package rest; import javax.ws.rs.*; @Path("/bienvenue") public class Bienvenue { @GET @Produces("text/plain") public String getMessage() { return "Bienvenue... (premier exemple REST)"; } } Bienvenue tant une classe Java annote par @Path, la ressource sera hberge l'URI /bienvenue. La mthode getMessage() est elle-mme annote par @GET afin d'indiquer qu'elle traitera les requtes HTTP GET et qu'elle produit du texte (le contenu est identifi par le type MIME text/plain). Pour accder la ressource, il suffit d'un client HTTP, simple navigateur par exemple, pouvant envoyer une requte GET vers l'URL http://localhost:8080/AWRest/bienvenue.
Il est galement possible d'utiliser un client HTTP qui permet de tester les services web de type REST en nous montrant les diffrentes en-ttes. Vous pouvez installer par exemple le pluggin Poster du navigateur Firefox (ou autre).
Le code prcdent montre que le service REST n'implmente aucune interface et n'tend aucune classe : @Path est la seule annotation obligatoire pour transformer un POJO en service REST. JAX-RS utilisant la configuration par exception, un ensemble d'annotations permettent de modifier son comportement par dfaut. Les exigences que doit satisfaire une classe pour devenir REST sont les suivantes : Elle doit tre annot par @javax.ws.rs.Path. Pour ajouter les fonctionnalits des EJB au service REST, la classe doit tre annote @javax.ejb.Stateless. Elle doit tre publique et ne pas tre finale ni abstraite. Les classes ressources racine (celles ayant une annotation @Path) doivent avoir un constructeur par dfaut public (ou aucuns). Les classes ressources non racine n'exigent pas ce constructeur. La classe ne doit pas dfinir la mthode finalyze(). Par nature, JAX-RS repose sur HTTP et dispose d'un ensemble de classes et d'annotations clairement dfinies pour grer HTTP et les URI. Une ressource pouvant avoir plusieurs reprsentations, l'API permet de grer un certain nombre de types de contenu et utilise JAXB pour srialiser et dsrialiser les reprsentations XML et JSON en objets. JAX-RS tant indpendant du conteneur, les ressources peuvent tre videmment dployes dans Glassfish, mais galement dans un grand nombre d'autres conteneurs de servlets.
session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.*; import javax.xml.bind.JAXBElement; @Path("/personnels") @Stateless @Produces({"application/xml", "application/json"}) @Consumes({"application/xml", "application/json"}) public class GestionPersonnel { @PersistenceContext private EntityManager bd; @Context private UriInfo uri; @GET public List<Personne> toutLePersonnel() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @POST public void nouveauPersonnel(JAXBElement<Personne> personne) { bd.persist(personne.getValue()); } }
entit.Personne.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @Entity @XmlRootElement @NamedQueries({ @NamedQuery(name="toutLePersonnel", query="SELECT p FROM Personne p ORDER BY p.nom, p.prnom"), @NamedQuery(name="recherchePersonnel", query="SELECT p FROM Personne p WHERE p.nom = :nom AND p.prnom = :prnom") }) public class Personne implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String nom; private String prnom; @Temporal(javax.persistence.TemporalType.DATE) private Date naissance; private String tlphone; public long getId() { return id; } public Date getNaissance() { return naissance; } public void setNaissance(Date naissance) { this.naissance = naissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom.toUpperCase(Locale.FRENCH); } public String getPrnom() { return prnom; } public void setPrnom(String prnom) { StringBuilder chaine = new StringBuilder(prnom.toLowerCase()); chaine.setCharAt(0, Character.toUpperCase(chaine.charAt(0))); this.prnom = chaine.toString(); } public String getTlphone() { return tlphone; } public void setTlphone(String tlphone) { this.tlphone = tlphone; } @Override public String toString() { return nom +" "+ prnom; } } Le code du bean session reprsente un service REST pouvant consommer et produire les reprsentations XML et JSON d'un personnel. La mthode toutLePersonnel() rcupre la liste de tout le personnel partir d'une base de donnes et renvoie sa reprsentation XML ou JSON (en utilisant la ngociation de contenu) ; elle est appele par une requte GET. La mthode nouveauPersonnel() prend une reprsentation XML ou JSON d'une personne et le stocke dans la base de donnes. Cette mthode est invoque par une requte POST et renvoie l'URI du nouveau livre.
rest.Bienvenue.java
package rest; import javax.ws.rs.*; @Path("/") // Changement du chemin de l'URI pour que cela corresponde la racine de l'application Web public class Bienvenue { @GET @Produces("text/plain") public String getMessage() { return "Bienvenue... (premier exemple REST)"; } } Vous pouvez galement intgrer dans la syntaxe de l'URI des modles de chemins d'URI au moyen d'un nom de variable entour d'accolades : ces variables seront values l'excution. @Path("/personnels/{nom}") @Path peut galement s'appliquer aux mthodes des ressources racine, ce qui permet de regrouper les fonctionnalits plusieurs ressources, comme le montre le code suivant :
session.GestionPersonnel.java
package session; import entit.Personne; import java.util.List; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.*; import javax.xml.bind.JAXBElement; @Path("/personnels") @Stateless @Produces({"application/xml", "application/json"}) @Consumes({"application/xml", "application/json"}) public class GestionPersonnel { @PersistenceContext private EntityManager bd; @Context private UriInfo uri; @GET public List<Personne> toutLePersonnel() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Path("{idPersonnel}") public Personne unPersonnel(@PathParam("idPersonnel") long idPersonnel) { return bd.find(Personne.class, idPersonnel); } @GET @Path("requete") public Personne unPersonnel(@QueryParam("nom") String nom, @QueryParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } @GET @Path("/premiers/") public List<Personne> dixPremiers() { Query requte = bd.createNamedQuery("toutLePersonnel"); requte.setMaxResults(10); return requte.getResultList(); } @GET @Path("/suivants/{page}") public List<Personne> dixSuivants(@PathParam("page") int page) { Query requte = bd.createNamedQuery("toutLePersonnel"); requte.setFirstResult(page); requte.setMaxResults(10*page); return requte.getResultList(); } @POST public void nouveauPersonnel(JAXBElement<Personne> personne) { bd.persist(personne.getValue()); }
@DELETE @Path("{idPersonnel}") public void supprimePersonnel(@PathParam("idPersonnel") long idPersonnel) { bd.remove(unPersonnel(idPersonnel)); } @PUT @Path("{idPersonnel}") public void modifiePersonnel(@PathParam("idPersonnel") long idPersonnel) { bd.merge(unPersonnel(idPersonnel)); } } Si @Path est applique la fois sur la classe et une mthode, le chemin relatif de la ressource produite par cette mthode est la concatnation de la classe et de la mthode. Ainsi, pour obtenir un personnel par son identifiant, par exemple, le chemin sera /personnels/1234. Cette recherche sera effectue au travers de la mthode unPersonnel(). Si nous demandons la ressource racine /personnels, seule la mthode sans annotation @Path sera automatiquement slectionne : ici toutLePersonnel(). Si nous demandons /personnels/premiers, c'est la mthode dixPremiers() qui sera invoque. Si nous dsirons avoir les dix personnels de la troisime page, c'est la mthode dixSuivants() qui sera invoque en proposant l'URI /personnels/suivants/3. Si @Path("/personnels") n'annotait que la classe et aucune mthode, le chemin d'accs de toutes les mthodes serait le mme et il faudrait alors utiliser le verbe HTTP (GET, PUT) et la ngociation du contenu (texte, XML, etc.) pour les diffrencier.
@QueryParam Cette annotation permet d'extraire la valeur d'un paramtre modle d'une URI. L aussi, le code suivant permet de retrouver un personnel partir de son nom et de son prnom au moyen de l'URI suivante : http://localhost:8080/RESTPersonnel/personnels?nom=REMY&prenom=Emmanuel : @Path("/personnels") public class GestionPersonnel { @GET
public Personne unPersonnel(@QueryParam("nom") String nom, @QueryParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } @MatrixParam Cette annotation agit comme @QueryParam, sauf qu'elle extrait la valeur d'un paramtre matrice d'une URI (le dlimiteur est ; au lieu de ?). Le code suivant permet galement, sous une autre forme, de retrouver un personnel partir de son nom et de son prnom avec l'URI suivante : http://localhost:8080/RESTPersonnel/personnels;nom=REMY;prenom=Emmanuel : @Path("/personnels") public class GestionPersonnel { @GET public Personne unPersonnel(@MatrixParam("nom") String nom, @MatrixParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } @CookieParam et @HeaderParam Deux autres mthodes sont lies aux dtails internes de HTTP, ce que nous ne voyons pas directement dans les URI : les cookies et les en-ttes. @CookieParam extrait la valeur d'un cookie, tandis que @HeaderParam permet d'obtenir la valeur d'un en-tte :
@Path("/") @Produces("text/plain") public class Bienvenue { @GET public String bienvenue() { return "Bienvenue... (premier exemple REST)"; } @GET @Path("paramtres") public String paramtres(@HeaderParam("Accept") String typeContenu, @HeaderParam("User-Agent") String agent) { return typeContenu+'\n'+agent; } } @FormParam Cette annotation prcise que la valeur d'un paramtre doit tre extraite d'un formulaire situ dans le corps de la requte : @Path("/personnels") public class GestionPersonnel { @GET @Path("formulaire") @Consumes("application/x-www-form-urlencoded") public Personne recherche(@FormParam("nom") String nom, @FormParam("prenom") String prnom) { Query requte = bd.createNamedQuery("recherchePersonnel"); requte.setParameter("nom", nom); requte.setParameter("prnom", prnom); return (Personne) requte.getSingleResult(); } ... } @DefaultValue Nous pouvons ajouter @DefaultValue toutes ces annotations pour dfinir une valeur par dfaut pour le paramtre que nous attendons. Cette valeur sera utilise si les mtadonnes correspondantes sont absentes de la requte. Si le paramtre age ne se trouve pas dans la requte, par exemple, le code suivant utilisera la valeur 50 par dfaut : @Path("/personnels") public class GestionPersonnel { @GET public Personne unPersonnel(@DefaultValue("50") @QueryParam("age") int ge) { ... } }
mdias changs entre le client et le serveur. L'utilisation de l'une de ces annotations sur une mthode redfinit celle qui s'appliquait sur la classe de la ressource pour un paramtre d'une mthode ou une valeur de retour. En leur absence, on suppose que la ressource supporte tous les types de mdia (*/*). L'expression du type de contenu (du type MIME) se fait au moyen d'une simple chaine de caractres, comme "text/plain" par exemple, ou au travers de la classe MediaType qui possde toutes les constantes prdfinies de chacun des types MIME connus, comme par exemple MediaType.TEXT_PLAIN. Dans le code qui suit, Bienvenue produit par dfaut une reprsentation en texte brut, sauf pour la premire mthode qui propose un rsultat sous forme de pas HTML.
rest.Bienvenue.java
package rest; import javax.ws.rs.*; @Path("/") @Produces("text/plain") public class Bienvenue { @GET @Produces("text/html") public String bienvenue() { return "<html><h2 align='center'>Bienvenue... (premier exemple REST)</h2></html>"; } @GET public String paramtres(@HeaderParam("Accept") String typeContenu, @HeaderParam("User-Agent") String agent) { return typeContenu+'\n'+agent; } } Si une ressource peut produire plusieurs types de mdia Internet, la mthode choisie correspondra au type qui convient le mieux l'en-tte Accept de la requte HTTP du client. Si, par exemple, cet en-tte est : Accept : text/plain et que l'URI est "/", c'est la mthode bienvenue() qui sera invoque. Le client aurait pu galement utiliser l'en-tte suivant : Accept : text/plain; q=0.8, text/html. Cet en-tte annonce que le client peut accepter les types text/plain et text/html mais qu'il prfre le dernier choix avec un facteur de qualit de 0.8 ("je prfre huit fois plus le text/html que le text/plain"). En incluant cet en-tte la mme requte "/", c'est la mthode paramtres() qui est cette fois-ci invoque :
Format JSON
JSON est un format lger pour l'change de donnes structures complexes. Il est l'image des documents XML en moins verbeux. Il est trs utile lorsque vous devez transfrer toutes les informations relative une entit par exemple. Voici ci-dessous un exemple de document JSON reprsentant une entit Personne qui peut disposer de plusieurs numros de tlphones : { "id" : 51,
Fournisseur d'entits
Lorsque les entits sont reues dans des requtes ou envoyes dans des rponses, l'implmentation JAX-RS doit pouvoir convertir les reprsentations en Java et vice versa : c'est le rle des fournisseurs d'entits. JAXB, par exemple, traduit un objet en reprsentation et rciproquement. Il existe deux variantes de fournisseur d'entits : MessageBodyReader et MessageBodyWriter. Requtes Pour traduire le corps d'une requte en Java, une classe doit implmenter l'interface javax.ws.rs.MessageBodyReader et tre annote par @Provider. Par dfaut, l'implmentation est cense consommer tous les types de mdias (*/*), mais l'annotation @Consumes permet de restreindre les types supports. Rponses De la mme faon, un type Java peut tre traduit en corps de rponse. Une classe Java voulant effectuer ce traitement doit implmenter l'interface javax.ws.rs.MessageBodyWriter et tre annote par l'interface @Provider. L'annotation @Produces indique les types de mdias supports. Fournisseurs d'entits par dfaut L'implmentation de JAX-RS offre un ensemble de fournisseurs d'entits par dfaut convenant aux situations courantes. Type byte[] java.lang.String java.io.InputStream java.io.Reader java.io.File java.activation.DataSource javax.xml.transform.Source javax.xml.bind.JAXBElement MultivalueMap<String,String> javax.ws.rs.core.StreamingOutput Srialisation automatique JAX-RS peut automatiquement effectuer des oprations de srialisation et d-srialisation vers un type Java spcifique - */* : byte[] - text/* : String - text/xml, application/xml, application/*+xml : JAXBElement - application/x-www-form-urlencoded : MultivalueMap<String,String> Gestion du contenu : InputStream Requte et rponse avec un flux dentre : @POST @Path("flux") public void lireFlux(InputStream is) throws IOException { byte[] octets = lireOctets(is); String input = new String(octets); System.out.println(input); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1000]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } @Path("flux") @GET @Produces(MediaType.TEXT_XML) public InputStream envoyerDocumentXML() throws FileNotFoundException { return new FileInputStream("exemple.xml"); } Gestion du contenu : File Requte et rponse avec un fichier : @Path("fichier") @PUT public void lireFichier(File file) throws IOException { byte[] octets = lireOctets(new FileInputStream(file)); String input = new String(octets); System.out.println(input); } @Path("fichier") @GET @Produces(MediaType.TEXT_XML) public File envoyerFichier() { File file = new File("exemple.xml"); return file; } Gestion du contenu : byte[] Requte et rponse avec un tableau d'octets : Description Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Tous les types de mdia (*/*) Type XML (text/xml, application/xml et application/-*+xml) Types de mdia XML de JAXB (text/-xml, application/xml et application/*+xml) Contenu de formulaire (application/x-www-form-urlencoded). Tous les types de mdias (*/*), uniquement MessageBodyWriter.
@Path("/") public class Bienvenue { @GET @Produces("text/plain") public byte[] get() { return "Bienvenue tous".getBytes(); } @POST @Consumes("text/plain") public void post(byte[] octets) { System.out.println(new String(octets)); } } Gestion du contenu : String Requte et rponse avec une chane de caractres @Path("chaine") @PUT public void contenuChaine(String current) throws IOException { System.out.println(current); } @Path("chaine") @GET @Produces(MediaType.TEXT_XML) public String envoiChaine() { return "<?xml version=\"1.0\"?>" + "<details>Bienvenue tous" +"</details>"; } Types personnaliss : format XML Gestion d'un personnel de l'entreprise @GET @Produces(MediaType.APPLICATION_XML) public List<Personne> toutLePersonnel() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Produces(MediaType.APPLICATION_XML) // Le type MIME retourn par le service ce qui permet au client de connatre le format traiter @Path("{idPersonnel}") public Personne unPersonnel(@PathParam("idPersonnel") long idPersonnel) { return bd.find(Personne.class, idPersonnel); } @POST @Consumes(MediaType.APPLICATION_XML) // Utilisation dun objet JAXBElement pour envelopper le type Personne public void nouveauPersonnel(JAXBElement<Personne> personne) { bd.persist(personne.getValue()); } @DELETE @Path("{idPersonnel}") public void supprimePersonnel(@PathParam("idPersonnel") long idPersonnel) { bd.remove(unPersonnel(idPersonnel)); } @PUT @Consumes(MediaType.APPLICATION_XML) public void modifiePersonnel(Personne personne) { bd.merge(personne); }
@Path("{idPersonnel}") public void supprimePersonnel(@PathParam("idPersonnel") long idPersonnel) { ... } } Lorsqu'une mthode ressource est invoque, les paramtres annots par l'une des mthodes d'extraction vues prcdemment sont initialiss. La valeur d'un paramtre non annot (appel paramtre entit) est obtenue partir du corps de la requte et convertie par un des fournisseurs d'entits vus prcdemment. Les mthodes peuvent retrouner void, Response ou un autre type Java. Response indique qu'il faudra fournir d'autres mtadonnes. Lorsque nous crons un nouvel agent de l'entreprise, par exemple, il serait judicieux de renvoyer son URI personnelle.
La classe Response
Actuellement, tous les services dvelopps retournaient soit un type void soit un type Java dfini par le dveloppeur. JAX-RS facilite la construction de rponses en permettant de choisir un code de retour, de fournir des paramtres dans len-tte, de retourner une URI, etc. Les rponses complexes sont dfinies par la classe Response disposant de mthodes abstraites non utilisables directement : Retour de l'information globale, ce que nous nommons l'entit : getEntity(). Status de la rponse avec son code de succs ou d'erreur : getStatus(). Donnes de l'en-tte sous forme de couple cl/valeur : getMetaData(). Les informations de ces mthodes sont obtenues par des mthodes statiques retournant des ResponseBuilder.
Informations contextuelles
Le fournisseur de ressources a besoin d'informations contextuelles pour traiter correctement une requte. L'annotation @javax.ws.rs.Context sert injecter les classes suivantes dans un attribut ou dans la paramtre d'une mthode : HttpHeaders (informations lies len-tte), UriInfo (informations lies aux URIs), Request (informations lies au traitement de la requte), SecurityContext (informations lies la scurit) et Providers. Ainsi, lannotation @Context permet dinjecter des objets lis au contexte de lapplication. Certains de ces objets permettent dobtenir les mmes informations que les prcdentes annotations lies aux paramtres. UriInfo Un objet de type UriInfo permet dextraire les informations brutes dune requte HTTP. Les principales mthodes sont les suivantes : - String getPath() : Chemin relatif de la requte. - MultivaluedMap<String, String> getPathParameters() : Valeurs des paramtres de la requte contenues dans Template Parameters. - MultivaluedMap<String, String> getQueryParameters() : Valeurs des paramtres de la requte. - URI getBaseUri() : Chemin de lapplication. - URI getAbsolutePath() : Chemin absolu (base + chemins). - URI getRequestUri() : Chemin absolu incluant les paramtres. @Path("/bienvenue") public class Bienvenue { @GET @Path("{param}") public void getInformationUriInfo(@Context UriInfo uriInfo, @PathParam("param") String param, @QueryParam("nom") String nom) { System.out.println("getPath() : " + uriInfo.getPath()); System.out.println("getAbsolutePath() : " + uriInfo.getAbsolutePath()); System.out.println("getBaseUri() : " + uriInfo.getBaseUri()); System.out.println("getRequestUri() : " + uriInfo.getRequestUri()); System.out.println("paramtre : "+ param); System.out.println("nom : "+ nom); System.out.println("getPathParameters() : "+uriInfo.getPathParameters()); System.out.println("getQueryParameters() : "+uriInfo.getQueryParameters()); } } Voici les rsultats obtenus sur le serveur d'application l'issue de cette requte http://localhost:8080/AWRest/bienvenue/test?nom=REMY Infos: getPath() : bienvenue/test Infos: getAbsolutePath() : http://localhost:8080/AWRest/bienvenue/test Infos: getBaseUri() : http://localhost:8080/AWRest/ Infos: getRequestUri() : http://localhost:8080/AWRest/bienvenue/test?nom=REMY Infos: paramtre : test Infos: nom : REMY Infos: getPathParameters() : {param=[test]} Infos: getQueryParameters() : {nom=[REMY]} Plutt que d'associer une information d'URI propose chacune des mthodes de la classe en spcifiant systmatiquement un paramtre supplmentaire, il peut tre judicieux de factoriser cette information en tant qu'attribut de la classe du web service REST. Ainsi, nous pourrons ventuellement exploiter ces informations pour chacune des mthodes de la classe. Voici le changement effectu de l'exemple prcdent : @Path("/bienvenue") public class Bienvenue { @Context UriInfo uriInfo; @GET @Path("{param}") public void getInformationUriInfo(@PathParam("param") String param, @QueryParam("nom") String nom) { System.out.println("getPath() : " + uriInfo.getPath()); System.out.println("getAbsolutePath() : " + uriInfo.getAbsolutePath()); System.out.println("getBaseUri() : " + uriInfo.getBaseUri()); System.out.println("getRequestUri() : " + uriInfo.getRequestUri()); System.out.println("paramtre : "+ param); System.out.println("nom : "+ nom); System.out.println("getPathParameters() : "+uriInfo.getPathParameters()); System.out.println("getQueryParameters() : "+uriInfo.getQueryParameters()); } } Les en-ttes : HttpHeaders Comme nous l'avons vu prcdemment, les informations transportes entre le client et le serveur sont formes non pas uniquement du corps d'une entit, mais galement d'en-ttes (Date, Content-type, etc.). Les en-ttes HTTP font partie de l'interface uniforme et les services web REST les utilisent. La classe javax.ws.rs.HttpHeaders peut tre injecte dans un attribut ou dans un paramtre de mthode au moyen de l'annotation @Context afin de permettre d'accder aux valeurs des en-ttes sans tenir compte de leur casse. Un objet de type HttpHeader permet dextraire les informations contenues dans len-tte dune requte. Les principales mthodes sont les suivantes : - Map<String, Cookie> getCookies() : les cookies de la requte. - Locale getLanguage() : la langue de la requte. - MultivaluedMap<String, String> getRequestHeaders() : valeurs des paramtres de len-tte de la requte. - MediaType getMediaType() : le type MIME de la requte. A noter que ces mthodes permettent dobtenir le mme rsultat que les annotations @HeaderParam et @CookieParam. @Path("/bienvenue") public class Bienvenue { @GET @Produces("text/plain") public Response get(@Context HttpHeaders enTtes) { StringBuilder chane = new StringBuilder(enTtes.getAcceptableLanguages().toString()); chane.append('\n'); chane.append(enTtes.getRequestHeader("accept-language")); return Response.ok ().entity(chane.toString()).build(); } }
L aussi, nous pouvons prendre un attribut de type HttpHeaders plutt de la prvoir pour chacune des mthodes de la classe du web service REST : @Path("/bienvenue") public class Bienvenue { @Context HttpHeaders enTtes; @GET @Produces("text/plain") public Response get() { StringBuilder chane = new StringBuilder(enTtes.getAcceptableLanguages().toString()); chane.append('\n'); chane.append(enTtes.getRequestHeader("accept-language")); return Response.ok ().entity(chane.toString()).build(); } }
Construction d'URI
Les liens hypertextes sont un lment central des applications REST. Afin d'voluer travers les tats de l'application, les services web REST doivent grer les transitions et la construction des URIs. Pour ce faire, JAX-RS fournit une classe javax.ws.rs.core.UriBuilder destine remplacer java.net.URI et faciliter la construction d'URI. UriBuilder dispose d'un ensemble de mthodes permettant de construire de nouvelles URIs ou d'en fabriquer partir d'URIs existantes. La classe utilitaire UriBuilder permet de construire des URIs complexes. Il est possible de construire des URIs avec UriBuilder via UriInfo (au moyen de l'annotation @Context comme prcdemment) o toutes URIs seront relatives au chemin de la requte. Voici les mthodes pour obtenir un UriBuilder : UriBuilder getBaseUriBuilder() : relatif au chemin de lapplication. UriBuilder getAbsolutePathBuilder() : relatif au chemin absolu (base+chemins). UriBuilder getRequestUriBuilder() : relatif au chemin absolu incluant les paramtres. Le principe dutilisation de la classe utilitaire UriBuilder est identique ResponseBuilder. Les principales mthodes sont les suivantes : URI build(Object... values) : construit une URI partir dune liste de valeurs pour les Template Parameters. UriBuilder queryParam(String name, Object...values) : ajoute des paramtres de requte. UriBuilder path(String path) : ajout un chemin de requte. UriBuilder fromUri(String uri) : nouvelle instance partir dune URI. UriBuilder host(String host) : modifie lURI de lhte. @Path("/bienvenue") public class Bienvenue { @Context UriInfo info; @GET public Response get() { UriBuilder fabrique = info.getAbsolutePathBuilder(); URI uri = fabrique.path("{nom}/{prenom}").queryParam("age", "{age}").build("REMY", "Emmanuel", 59); return Response.created (uri).build(); } }
Malheureusement, ce monde n'existe pas et, tt ou tard, une ressource nous exploser en plein visage parce que les donnes reues ne sont pas valides ou que des parties du rseau ne sont pas fiable. Comme le montre le code suivant, nous pouvons lever tout instant une exception WebApplicationException ou l'une de ses sous-classes dans un fournisseur de ressources. Cette exception sera capture par l'implmentation de JAX-RS et convertie en rponse HTTP. L'erreur par dfaut est un code 500 avec un message vide, mais la classe javax.ws.rs.WebApplicationException offre diffrents constructeurs permettant de choisir un code d'tat spcifique (dfini dans l'numration javax.ws.rs.core.Response.Status) ou une entit. Les exceptions non controles et les erreurs qui n'entrent pas dans les deux cas prcdents seront relances comme toute exception Java non contrle.
rest.Bienvenue.java
@Path("/bienvenue") public class Bienvenue { @GET @Path("age/{age}") @Produces("text/html") public String getAge(@PathParam("age") int age) { if (age<0) throw new WebApplicationException(Response.status(400).entity("Vous devez donner une valeur positive...").build()); return "Age = "+age+ " ans"; } }
Cycle de vie
Lorsqu'une requte arrive, la ressource cible est rsolue et une nouvelle instance de la classe ressource racine correspondante est cre. Le cycle de vie d'une classe ressource racine dure donc le temps d'une requte, ce qui implique que la classe ressource n'a pas s'occuper des problmes de concurrence et qu'elle peut donc utiliser les variables d'instance en toute scurit. S'ils sont employs dans un conteneur Java EE (servlet ou EJB), les classes ressources et les fournisseurs JAX-RS peuvent galement utiliser les annotations de gestion de cycle de vie et de la scurit : @PostConstruct, @Predestroy, @RunAs, @RolesAllowed, @PermitAll, @DenyAll et @DeclareRoles. Le cycle de vie d'une ressource peut ainsi se servir de @PostConstruct et de @PreDestroy pour ajouter de la logique mtier respectivement aprs la cration d'une ressource et avant sa suppression.
Je vous donne l'ensembles des codes ncessaire cette application web suivi des diffrents tests pour valuer le fonctionnement correct du service web REST.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" ... <context-param> <param-name>primefaces.THEME</param-name> <param-value>sunny</param-value>
</context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app> Nous remarquons ici la prsence de deux servlets, la premire relative JSF qui sert de contrleur pour les requtes classiques de l'application web, la deuxime s'occupant plus particulirement des fonctionnalits du service web REST.
entit.Personne.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQuery(name="toutLePersonnel", query="SELECT p FROM Personne p ORDER BY p.nom, p.prnom") public class Personne implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String nom; private String prnom; @Temporal(javax.persistence.TemporalType.DATE) private Date naissance; private String tlphone; public Personne() {} public Personne(String nom, String prnom, Date naissance, String tlphone) { setNom(nom); setPrnom(prnom); this.naissance = naissance; this.tlphone = tlphone; } public long getId() { return id; } public Date getNaissance() { return naissance; } public void setNaissance(Date naissance) { this.naissance = naissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom.toUpperCase(Locale.FRANCE); } public String getTlphone() { return tlphone; } public void setTlphone(String tlphone) { this.tlphone = tlphone; } public String getPrnom() { return prnom; } public void setPrnom(String prnom) { StringBuilder chane = new StringBuilder(prnom.toLowerCase(Locale.FRANCE)); chane.setCharAt(0, Character.toUpperCase(chane.charAt(0))); this.prnom = chane.toString(); } @Override public String toString() { return nom + " " + prnom; } } Il s'agit ici de l'entit Personne. Remarquez au passage que cette entit possde l'annotation indispensable @XmlRootElement si vous souhaitez soumettre cette entit au format JSON.
service.GestionPersonnel.java
package service; import entit.Personne; import java.util.*; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.Response; @Path("/") @Stateless public class GestionPersonnel { @PersistenceContext private EntityManager bd;
@GET @Produces("application/json") @Path("tous") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Produces("application/json") @Path("json/{id}") public Personne rechercheJSON(@PathParam("id") long id) { return bd.find(Personne.class, id); } @GET @Path("{id}") public Response rechercheDtaille(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); return Response.ok ().header("nom", personne.getNom()) .header("prenom", personne.getPrnom()) .header("date", personne.getNaissance().getTime()) .header("telephone", personne.getTlphone()).build(); } @POST public Response nouveau(@HeaderParam("nom") String nom, @HeaderParam("prenom") String prnom, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = new Personne(nom, prnom, new Date(date), tlphone); bd.persist(personne); return Response.ok ().header("id", personne.getId()).build(); } @POST @Path("{nom}/{prnom}/{tlphone}") public Response nouveau(@PathParam("nom") String nom, @PathParam("prnom") String prnom, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = new Personne(nom, prnom, new GregorianCalendar(anne, mois-1, jour).getTime(), tlphone); bd.persist(personne); return Response.ok ().header("id", personne.getId()).build(); } @POST @Path("json") @Consumes("application/json") public void nouveau(Personne personne) { bd.persist(personne); } @PUT @Path("enTte/{id}") public void modifier(@PathParam("id") long id, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new Date(date)); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/{tlphone}") public void modifier(@PathParam("id") long id, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/telephone({tlphone})") public void modifier(@PathParam("id") long id, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/jour={jour}-mois={mois}-annee={annee}") public void modifier(@PathParam("id") long id, @PathParam("annee") int anne, @PathParam("mois") int mois, @PathParam("jour") int jour) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); bd.merge(personne); } @PUT @Path("json") @Consumes("application/json") public void modifier(Personne personne) { bd.merge(personne);
} @DELETE @Path("{id}") public void supprimer(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); bd.remove(personne); } @DELETE @Path("json") @Consumes("application/json") public void supprimer(Personne personne) { Personne recherche = bd.find(Personne.class, personne.getId()); bd.remove(recherche); } } Le bean session GestionPersonnel sert la fois pour l'application web classique avec des mthodes qui seront utilises en local par le bean reprsentant le modle de la structure JSF, mais rend aussi service l'extrieur directement, la fois par les mmes mthodes, mais galement par des mthodes plus spcifiques pour le service web REST.
bean.Personnel.java
package bean; import entit.Personne; import java.util.List; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.faces.application.FacesMessage; import javax.faces.bean.*; import javax.faces.context.FacesContext; import service.GestionPersonnel; @ManagedBean @ViewScoped public class Personnel { private Personne personne; @EJB private GestionPersonnel gestion; private List<Personne> tous; private int indice; @PostConstruct private void init() { tous = gestion.listePersonnels(); if (tous.isEmpty()) personne = new Personne(); else { indice = tous.size()-1; personne = tous.get(indice); } } public Personne getPersonne() { return personne; } public void setPersonne(Personne personne) { this.personne = personne; } public List<Personne> getTous() { return tous; } public boolean isNouveau() { return personne.getId() == 0; } public void nouveau() { personne = new Personne(); message("Nouvel agent", "Saisissez l'ensemble des coordonnes"); } public void enregistrer() { if (isNouveau()) gestion.nouveau(personne); else gestion.modifier(personne); init(); message("Enregistrement", personne+" est bien enregistre"); } public void supprimer() { message("Suppression", personne+" ne fait plus partie du personnel"); gestion.supprimer(personne); init(); } public void prcdent() { if (indice>0) indice--; personne = tous.get(indice); } public void suivant() { if (indice<tous.size()-1) indice++; personne = tous.get(indice); } private void message(String titre, String dtail) { FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(titre, dtail)); } } Ce code correspond au bean manag Personnel qui utilise les comptences du bean session GestionPersonnel vu prcdemment. Il sert de modle dans la structure JSF et il est en troite relation avec la vue reprsente par la page web index.xhtml.
index.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Gestion du personnel</title> </h:head> <h:body> <h:form> <p:growl autoUpdate="true" showDetail="true" /> <p:panel header="Gestion du personnel"> <p:accordionPanel activeIndex="-1"> <p:tab title="Liste du personnel"> <p:outputPanel autoUpdate="true"> <p:dataTable var="personne" value="#{personnel.tous}" paginator="true" rows="7"> <p:column> <f:facet name="header"><h:outputText value="Nom" /></f:facet> <h:outputText value="#{personne.nom}" /> </p:column> <p:column> <f:facet name="header"><h:outputText value="Prnom" /></f:facet> <h:outputText value="#{personne.prnom}" /> </p:column> <p:column> <f:facet name="header"><h:outputText value="Date de naissance" /></f:facet> <p:calendar locale="fr" value="#{personne.naissance}" pattern="EEEE dd MMMM yyyy" disabled="true"/> </p:column> <p:column> <f:facet name="header"><h:outputText value="Tlphones" /></f:facet> <h:outputText var="tlphone" value="#{personne.tlphone}" /> </p:column> </p:dataTable> </p:outputPanel> </p:tab> <p:tab title="Edition de chaque agent"> <p:outputPanel autoUpdate="true"> <p:panelGrid columns="2"> <h:outputText value="Nom :" /> <p:inputText value="#{personnel.personne.nom}" disabled="#{not personnel.nouveau}" /> <h:outputText value="Prnom :" /> <p:inputText value="#{personnel.personne.prnom}" disabled="#{not personnel.nouveau}" /> <h:outputText value="Date de naissance :" /> <p:calendar locale="fr" value="#{personnel.personne.naissance}" beforeShowDay="true" pattern="dd MMMM yyyy" /> <h:outputText value="Tlphone :" /> <p:inputMask value="#{personnel.personne.tlphone}" mask="99-99-99-99-99" /> <f:facet name="footer"> <p:commandButton value="Nouveau" action="#{personnel.nouveau()}" icon="ui-icon-document"/> <p:commandButton value="Enregistrer" action="#{personnel.enregistrer()}" icon="ui-icon-disk"/> <p:commandButton value="Supprimer" action="#{personnel.supprimer()}" disabled="#{personnel.nouveau}" icon="ui-icon-trash" /> <p:commandButton value="Prcedent" action="#{personnel.prcdent()}" /> <p:commandButton value="Suivant" action="#{personnel.suivant()}" /> </f:facet> </p:panelGrid> </p:outputPanel> </p:tab> </p:accordionPanel> </p:panel> </h:form> </h:body> </html> Il s'agit ici de la vue prsente au navigateur lorsque nous lanons l'application web, la page web index.xhtml.
@GET @Produces("application/json") @Path("tous") public List<Personne> listePersonnels() { Query requte = bd.createNamedQuery("toutLePersonnel"); return requte.getResultList(); } @GET @Produces("application/json") @Path("json/{id}") public Personne rechercheJSON(@PathParam("id") long id) { return bd.find(Personne.class, id); } @GET @Path("{id}") public Response rechercheDtaille(@PathParam("id") long id) { Personne personne = bd.find(Personne.class, id); return Response.ok ().header("nom", personne.getNom()) .header("prenom", personne.getPrnom()) .header("date", personne.getNaissance().getTime()) .header("telephone", personne.getTlphone()).build(); } ... } Ce service propose trois mthodes avec l'annotation @GET. Le premire permet de restituer l'ensemble du personnel au format JSON. Voici ce que nous obtenons respectivement au travers d'un navigateur et du pluggin Poster. Dans ce dernier cas, les accents sont pris en compte. Remarquez bien au passage l'URI soumettre pour que ce service soit oprationnel.
La deuxime mthode permet de retourner un personnel galement au format JSON partir de son identifiant :
Enfin, la dernire mthode retourne galement un seul personnel, mais les informations retournes se situent dans l'en-tte de la rponse :
La deuxime mthode permet galement de crer un nouveau personnel directement au travers de l'URI. L'avantage d'utiliser des paramtres de type @QueryParam, c'est que lorsque nous laborons notre URI, l'ordre des paramtres n'a pas d'importance du moment que nous les voquons tous.
Enfin, la troisime mthode, qui sert galement pour le bean gr, permet au travers du service web REST de gnrer un nouveau personnel directement partir d'un document JSON. Comme l'entit possde l'annotation @XmlRootElement, le mapping entre le document JSON et la classe Personne se fait automatiquement.
Modification des informations dj enregistres - Utilisation de la mthode PUT du service web REST
@Path("/") @Stateless public class GestionPersonnel {
@PersistenceContext private EntityManager bd; @PUT @Path("enTte/{id}") public void modifier(@PathParam("id") long id, @HeaderParam("date") long date, @HeaderParam("telephone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new Date(date)); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/{tlphone}") public void modifier(@PathParam("id") long id, @QueryParam("annee") int anne, @QueryParam("mois") int mois, @QueryParam("jour") int jour, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/telephone({tlphone})") public void modifier(@PathParam("id") long id, @PathParam("tlphone") String tlphone) { Personne personne = bd.find(Personne.class, id); personne.setTlphone(tlphone); bd.merge(personne); } @PUT @Path("{id}/jour={jour}-mois={mois}-annee={annee}") public void modifier(@PathParam("id") long id, @PathParam("annee") int anne, @PathParam("mois") int mois, @PathParam("jour") int jour) { Personne personne = bd.find(Personne.class, id); personne.setNaissance(new GregorianCalendar(anne, mois-1, jour).getTime()); bd.merge(personne); } @PUT @Path("json") @Consumes("application/json") public void modifier(Personne personne) { bd.merge(personne); } ... } Ce service propose cette fois-ci quatre mthodes avec l'annotation @PUT. Le premire permet de modifier le numro de tlphone ainsi que la date de naissance en plaant les nouvelles valeurs dans l'en-tte de la requte, en spcifiant l'identifiant du personnel modifier directement dans l'URI.
La deuxime mthode modifie galement le numro de tlphone et la date de naissance du personnel identifi, mais cette fois-ci tout doit tre spcifi directement dans l'URI et au travers de paramtres de requte :
La dernire version permet de modifier la totalit du personnel l'aide d'un document JSON.
Dans le deuxime cas, nous envoyons la totalit des informations du personnel au travers d'un document JSON.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.rest" android:versionCode="1" android:versionName="1.0"> <application android:label="Client REST Personnel" > <activity android:name="ListePersonnel" android:label="Liste du Personnel"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="ChoixServeur" android:label="Choix du serveur" android:theme="@android:style/Theme.Dialog" /> <activity android:name="Personnel" android:label="Edition du Personnel" android:theme="@android:style/Theme.Dialog"/> </application> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
res/drawable/fond.xml
<?xml version="1.0" encoding="UTF-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:startColor="#FF0000" android:endColor="#FFFF00" android:type="radial" android:gradientRadius="300" /> </shape>
res/layout/adresse.xml
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="2dp"> <Button android:id="@+id/ok" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" android:onClick="ok"/> <EditText android:layout_toLeftOf="@id/ok" android:id="@+id/adresse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Adresse IP" /> </RelativeLayout>
fr.btsiris.rest.ChoixServeur.java
package fr.btsiris.rest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.*; import android.widget.EditText; public class ChoixServeur extends Activity { private EditText adresse; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.adresse); adresse = (EditText) findViewById(R.id .adresse); }
public void ok(View vue) { Intent intent = new Intent(); intent.putExtra("adresse", adresse.getText().toString()); setResult(RESULT_OK, intent); finish(); } }
res/layout/liste.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/fond" android:padding="3dp"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Nouvelle personne" android:onClick="edition" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
fr.btsiris.rest.Personne.java
package fr.btsiris.rest;
import java.io.Serializable; public class Personne implements Serializable { private long id; private String nom; private String prenom; private long naissance; private String telephone; public long getId() { return id; } public void setId(long id) { this.id = id; } public long getNaissance() { return naissance; } public void setNaissance(long naissance) { this.naissance = naissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { this.prenom = prenom; } @Override public String toString() { return nom + " " + prenom; } }
fr.btsiris.rest.ListePersonnel.java
package fr.btsiris.rest; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.*; import java.io.IOException; import java.util.*; import org.apache.http.HttpResponse; import org.apache.http.client.*; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.*; public class ListePersonnel extends ListActivity { private ArrayList<Personne> personnes = new ArrayList<Personne>(); private String adresse; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.liste); startActivityForResult(new Intent(this, ChoixServeur.class), 1); } protected void onActivityResult(int requestCode, int resultCode, Intent intention) { if (resultCode == RESULT_OK) { adresse = intention.getStringExtra("adresse"); try { miseAJour(); } catch (Exception ex) { Toast.makeText(this, "Rponse incorrecte", Toast.LENGTH_SHORT).show(); } } } @Override protected void onResume() { super.onResume(); if (adresse!=null) try { miseAJour();
} catch (Exception ex) { } } @Override protected void onStop() { super.onStop(); finish(); } public void miseAJour() throws IOException, JSONException { HttpClient client = new DefaultHttpClient(); HttpGet requete = new HttpGet("http://"+adresse+":8080/Personnel/rest/tous/"); HttpResponse reponse = client.execute(requete); if (reponse.getStatusLine().getStatusCode() == 200) { Scanner lecture = new Scanner(reponse.getEntity().getContent()); StringBuilder contenu = new StringBuilder(); while (lecture.hasNextLine()) contenu.append(lecture.nextLine()+'\n'); JSONArray tableauJSON = new JSONArray(contenu.toString()); StringBuilder message = new StringBuilder(); personnes.clear(); for (int i=0; i<tableauJSON.length(); i++) { JSONObject json = tableauJSON.getJSONObject(i); Personne personne = new Personne(); personne.setId(json.getLong("id")); personne.setNom(json.getString("nom")); personne.setPrenom(json.getString("prnom")); personne.setNaissance(json.getLong("naissance")); personne.setTelephone(json.getString("tlphone")); personnes.add(personne); } setListAdapter(new ArrayAdapter<Personne>(this, android.R.layout.simple_list_item_1 , personnes)); } else Toast.makeText(this, "Problme de communication", Toast.LENGTH_SHORT).show(); } public void edition(View vue) { Intent intention = new Intent(this, Personnel.class); intention.putExtra("id", 0); intention.putExtra("adresse", adresse); startActivity(intention); } @Override protected void onListItemClick(ListView liste, View vue, int position, long id) { Personne personne = personnes.get(position); Intent intention = new Intent(this, Personnel.class); intention.putExtra("id", personne.getId()); intention.putExtra("adresse", adresse); intention.putExtra("nom", personne.getNom()); intention.putExtra("prenom", personne.getPrenom()); intention.putExtra("naissance", personne.getNaissance()); intention.putExtra("telephone", personne.getTelephone()); startActivity(intention); } }
res/layout/personnel.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/fond" android:padding="3px"> <EditText android:id="@+id/nom" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Nom" /> <EditText android:id="@+id/prenom" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Prnom" /> <EditText android:id="@+id/naissance" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Date naissance" android:onClick="changeDate"/> <EditText android:id="@+id/telephone" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="n tlphone"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Nouveau" android:onClick="nouveau"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enregistrer" android:onClick="enregistrer"/> <Button android:id="@+id/supprimer" android:layout_width="wrap_content"
fr.btsiris.rest.Personnel.java
package fr.btsiris.rest; import android.app.*; import android.content.Intent; import android.os.Bundle; import android.text.format.DateFormat; import android.view.*; import android.widget.*; import java.io.IOException; import java.util.*; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.*; import org.apache.http.impl.client.DefaultHttpClient; public class Personnel extends Activity { private EditText nom, prenom, naissance, telephone; private Button supprimer; private long id; private String adresse; private Calendar calendrier = Calendar.getInstance(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.personnel); nom = (EditText) findViewById(R.id .nom); prenom = (EditText) findViewById(R.id .prenom); telephone = (EditText) findViewById(R.id .telephone); naissance = (EditText) findViewById(R.id .naissance); supprimer = (Button) findViewById(R.id .supprimer); } @Override protected void onStart() { super.onStart(); Intent intention = getIntent(); Bundle donnees = intention.getExtras(); id = donnees.getLong("id"); adresse = donnees.getString("adresse"); if (id==0) toutEffacer(); else { nom.setEnabled(false); prenom.setEnabled(false); supprimer.setEnabled(true); nom.setText(donnees.getString("nom")); prenom.setText(donnees.getString("prenom")); long date = donnees.getLong("naissance"); calendrier.setTimeInMillis(date); naissance.setText(DateFormat.format("EEEE dd MMMM yyyy", date)); telephone.setText(donnees.getString("telephone")); } } private void toutEffacer() { id = 0; nom.setEnabled(true); prenom.setEnabled(true); supprimer.setEnabled(false); nom.setText(""); prenom.setText(""); naissance.setText(""); telephone.setText(""); calendrier = Calendar.getInstance(); } private DatePickerDialog.OnDateSetListener evt = new DatePickerDialog.OnDateSetListener() { public void onDateSet(DatePicker dialog, int annee, int mois, int jour) { calendrier.set(annee, mois, jour); naissance.setText(DateFormat.format("EEEE dd MMMM yyyy", calendrier)); } }; public void changeDate(View vue) { new DatePickerDialog(this, evt, calendrier.get(Calendar.YEAR), calendrier.get(Calendar.MONTH), calendrier.get(Calendar.DAY_OF_MONTH)).show(); } public void nouveau(View vue) { toutEffacer(); } public void enregistrer(View vue) throws IOException { if (id==0) nouveauPersonnel(); else modifierPersonne(); } public void supprimer(View vue) throws IOException { HttpClient client = new DefaultHttpClient(); HttpDelete requete = new HttpDelete("http://"+adresse+":8080/Personnel/rest/"+id); client.execute(requete); Toast.makeText(this, "Personnel "+id+" supprim", Toast.LENGTH_SHORT).show(); finish();
} private void nouveauPersonnel() throws IOException { HttpClient client = new DefaultHttpClient(); HttpPost requete = new HttpPost("http://"+adresse+":8080/Personnel/rest/"); requete.addHeader("nom", nom.getText().toString()); requete.addHeader("prenom", prenom.getText().toString()); requete.addHeader("date", ""+calendrier.getTimeInMillis()); requete.addHeader("telephone", telephone.getText().toString()); client.execute(requete); Toast.makeText(this, "Nouveau personnel enregistr", Toast.LENGTH_SHORT).show(); finish(); } private void modifierPersonne() throws IOException { HttpClient client = new DefaultHttpClient(); HttpPut requete = new HttpPut("http://"+adresse+":8080/Personnel/rest/enTte/"+id); requete.setHeader("date", ""+calendrier.getTimeInMillis()); requete.setHeader("telephone", telephone.getText().toString()); client.execute(requete); Toast.makeText(this, "Personnel modifi", Toast.LENGTH_SHORT).show(); } }
Je vous donne l'ensemble des codes ncessaires cette application web suivi des diffrents tests pour valuer le bon fonctionnement du service web REST.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <context-param> <param-name>primefaces.THEME</param-name> <param-value>sunny</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping>
<welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app> Nous remarquons ici la prsence de deux servlets, la premire relative JSF qui sert de contrleur pour les requtes classiques de l'application web, la deuxime s'occupant plus particulirement des fonctionnalits du service web REST.
service.Archivage.java
package service; import java.io.*; import javax.annotation.PostConstruct; import javax.faces.bean.*; import javax.ws.rs.*; @Path("/") @ManagedBean public class Archivage { private final String rpertoire = "ArchivagePhotos/"; @PostConstruct private void init() { File rep = new File(rpertoire); if (!rep.exists()) rep.mkdir(); } @GET @Path("liste") @Produces("application/json") public String[] getPhotos() { return new File(rpertoire).list(); } @GET @Path("{nomFichier}") @Produces("image/jpeg") public InputStream restituer(@PathParam("nomFichier") String nom) throws FileNotFoundException { return new FileInputStream(rpertoire+nom); } @POST @Path("{nomFichier}") @Consumes("image/jpeg") public void stocker(@PathParam("nomFichier") String nom, InputStream flux) throws IOException { byte[] octets = lireOctets(flux); FileOutputStream fichier = new FileOutputStream(rpertoire+nom); fichier.write(octets); fichier.close(); } @DELETE @Path("{nomFichier}") public void supprimer(@PathParam("nomFichier") String nom) { new File(rpertoire+nom).delete(); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } } La grande particularit ici, c'est que notre service web REST est galement un bean manag qui va tre galement utile pour la vue de la structure JSF. Par contre, il ne s'agit pas d'un bean session puisque la persistance, au travers d'entit, n'est pas gre. Un rpertoire de stockage est prvu directement dans le serveur d'application utilis. Cela se produit la premire fois que le service entre en action au moyen de la mthode de rappel init(), grce l'annotation @PostConstruct. Nous retrouvons ensuite tous les mthodes classiques du protocole HTTP au moyen des annotation spcifiques @GET, @POST et @DELETE. Seule la mthode @PUT n'est pas utilise ici puisqu'elle ne correspond aucune fonctionnalit adapte ce genre de problme. Une autre grande particularit, que nous dcouvrirons tout l'heure, c'est que la mthode restituer() va galement tre utilise par la vue afin de proposer le flux de chaque image la galerie prsente.
index.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Archivage de photos</title> </h:head> <h:body> <h:form> <p:panel header="Archivage de photos"> <p:galleria value="#{archivage.photos}" var="photo"> <p:graphicImage value="rest/#{photo}" /> </p:galleria> </p:panel> </h:form> </h:body> </html>
La vue de index.xhtml est extrmement simple. La particularit, comme je viens de l'voquer, est d'utiliser le bean grer comme service web REST en ce qui concerne la visualisation de chaque photo, au travers de la mthode restituer() qui renvoie le flux de la photo concerne.
Enregistrement d'une nouvelle photo - Utilisation de la mthode POST du service web REST
@Path("/") @Produces("image/jpeg") @Consumes("image/jpeg") @ManagedBean public class Archivage { private final String rpertoire = "ArchivagePhotos/"; ... @POST @Path("{nomFichier}") public void stocker(@PathParam("nomFichier") String nom, InputStream flux) throws IOException { byte[] octets = lireOctets(flux); FileOutputStream fichier = new FileOutputStream(rpertoire+nom); fichier.write(octets); fichier.close(); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } } Voici la procdure suivre pour intgrer une nouvelle photo dans votre service. Il suffit de prciser le nom de fichier dans l'URI et slectionner le fichier image qui est le contenu de la requte :
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.photos" android:versionCode="1" android:versionName="1.0"> <application android:label="Stockage photos" > <activity android:name="ArchivagePhotos" android:label="Stocker vos photos" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="ChoixServeur" android:label="Choix du serveur" android:theme="@android:style/Theme.Dialog" /> </application> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
res/layout/adresse.xml
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="2dp"> <Button android:id="@+id/ok" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" android:onClick="ok"/> <EditText android:layout_toLeftOf="@id/ok" android:id="@+id/adresse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Adresse IP" /> </RelativeLayout>
fr.btsiris.rest.ChoixServeur.java
package fr.btsiris.rest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.*; import android.widget.EditText; public class ChoixServeur extends Activity { private EditText adresse; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.adresse); adresse = (EditText) findViewById(R.id .adresse); } public void ok(View vue) { Intent intent = new Intent(); intent.putExtra("adresse", adresse.getText().toString()); setResult(RESULT_OK, intent); finish(); } }
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#008800" android:padding="3dp"> <EditText android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Description de la photo" android:layout_alignParentBottom="true"/> <LinearLayout android:id="@+id/boutons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@id/description"> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Photographier" android:onClick="photographier" /> <Button android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="Envoyer" android:onClick="envoyer" /> </LinearLayout> <ImageView android:id="@+id/image" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:layout_above="@id/boutons" /> </RelativeLayout>
fr.btsiris.rest.ArchivagePhotos.java
package fr.btsiris.photos; import android.app.Activity; import android.content.Intent; import android.graphics.*; import android.net.Uri; import android.os.*; import android.provider.MediaStore; import android.view.View; import android.widget.*; import java.io.*; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.FileEntity; import org.apache.http.impl.client.DefaultHttpClient; public class ArchivagePhotos extends Activity { private EditText description; private ImageView image; private Uri fichierUri; private String adresse; private final int RECHERCHE_ADRESSE = 1; private final int PHOTOGRAPHIER = 2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); description = (EditText) findViewById(R.id .description); image = (ImageView) findViewById(R.id .image); startActivityForResult(new Intent(this, ChoixServeur.class), RECHERCHE_ADRESSE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intention) { switch (requestCode) { case RECHERCHE_ADRESSE : adresse = intention.getStringExtra("adresse"); break; case PHOTOGRAPHIER : File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); Bitmap photo = BitmapFactory.decodeFile(fichier.getPath()); image.setImageBitmap(photo); break; } } public void photographier(View vue) { Intent intention = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); fichierUri = Uri.fromFile(fichier); intention.putExtra(MediaStore.EXTRA_OUTPUT, fichierUri); startActivityForResult(intention, PHOTOGRAPHIER); } public void envoyer(View vue) throws IOException { HttpClient client = new DefaultHttpClient(); HttpPost requete = new HttpPost("http://"+adresse+":8080/Photos/rest/"+description.getText().toString()+".jpg"); requete.setEntity(new FileEntity(new File(Environment.getExternalStorageDirectory(), "photo.jpg"), "image/jpeg")); client.execute(requete); } }
Je vous donne l'ensembles des codes ncessaires ce service. Pour que le projet fonctionne correstement, vous avez un certain nombre d'archives intgrer : primefaces-3.2.jar : Archive des composants de haut niveau pour JSF. afterdark-1.0.5.jar : Thme utilis dans ce projet en relation avec PrimeFaces. commons-fileupload-1.2.2.jar : Ncessaire pour permettre l'archivage des photos, en relation avec PrimeFaces. commons-io-2.1.jar : Egalement ncessaire pour permettre l'archivage des photos, en relation avec PrimeFaces. metadata-extractor-2.5.0-RC3.jar : Extraction des donnes EXIF dans les photos.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <context-param> <param-name>primefaces.THEME</param-name> <param-value>afterdark</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <filter> <filter-name>PrimeFaces FileUpload Filter</filter-name> <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class> </filter> <filter-mapping> <filter-name>PrimeFaces FileUpload Filter</filter-name> <servlet-name>Faces Servlet</servlet-name> </filter-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app>
index.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Archivage de photos</title> </h:head> <script src="http://maps.google.com/maps/api/js?sensor=true" type="text/javascript" /> <h:body style="background-color: darkseagreen"> <h:form> <p:growl autoUpdate="true" showDetail="true" /> <p:panel header="Archivage de photos"> <p:outputPanel autoUpdate="true"> <p:selectOneMenu value="#{archivage.titre}"> <f:selectItems value="#{archivage.listeDesLots}" /> <p:ajax listener="#{archivage.changeLot()}"/> </p:selectOneMenu>
</p:outputPanel> <p:tabView> <p:tab title="Gallerie des photos"> <p:outputPanel autoUpdate="true"> <p:galleria value="#{archivage.lot.photos}" var="photo" effect="slide" filmstripPosition="right" showOverlays="true" frameWidth="90" frameHeight="60" > <p:graphicImage value="rest/#{photo.id}" width="600" height="400" styleClass="ui-corner-all" /> <p:galleriaOverlay title="#{photo}"> <h:outputText value="#{photo.jour}" rendered="#{photo.exif}"> <f:convertDateTime dateStyle="full" /> </h:outputText> <br /> <h:outputText value="(Lat. = #{photo.latitude}, Lon.= #{photo.longitude})" rendered="#{photo.exif}" /> <h:outputText value="Aucunes donnes EXIF" rendered="#{not photo.exif}" /> </p:galleriaOverlay> </p:galleria> </p:outputPanel> </p:tab> <p:tab title="Google Map"> <p:outputPanel autoUpdate="#{archivage.miseAJour}"> <p:gmap zoom="15" type="HYBRID" center="#{archivage.centre}" style="width:600px;height:400px" model="#{archivage.cartographie}"> <p:ajax event="overlaySelect" listener="#{archivage.choixMarqueur}" /> <p:gmapInfoWindow> <p:graphicImage value="rest/#{archivage.marqueur.data}" width="200" /><br /> <h:outputText value="#{archivage.marqueur.title}" /> </p:gmapInfoWindow> </p:gmap> </p:outputPanel> </p:tab> <p:tab title="Edition des photos"> <p:outputPanel autoUpdate="true"> Titre : <p:inputText value="#{archivage.lot.titre}" /> <p:commandButton icon="ui-icon-disk" value="Changer le titre" action="#{archivage.changeTitre()}" /> <p:commandButton icon="ui-icon-trash" value="Supprimer totalement ce lot" action="#{archivage.supprimeLot()}" /> <p:dataTable value="#{archivage.lot.photos}" var="photo" paginator="true" rows="4"> <p:column> <f:facet name="header">Photo</f:facet> <a target="_blank" href="rest/#{photo.id}"> <p:graphicImage value="rest/#{photo.id}" width="200" styleClass="ui-corner-all" id="image"/> </a> <p:tooltip for="image" value="Cliquez pour avoir un Zoom sur cette photo" showEffect="slide" /> </p:column> <p:column> <f:facet name="header">Traitements annexes</f:facet> <p:inputText value="#{photo.description}" /> <p:commandButton icon="ui-icon-disk" value="Changer la description" action="#{archivage.changeDescription(photo)}" /> <p:commandButton icon="ui-icon-trash" value="Supprimer la photo" action="#{archivage.supprimePhoto(photo)}" /> </p:column> </p:dataTable> </p:outputPanel> </p:tab> <p:tab title="Ajouter une nouvelle photo"> Description obligatoire de la photo : <p:inputText value="#{archivage.description}"> <p:ajax event="keyup" /> </p:inputText> <p:fileUpload fileUploadListener="#{archivage.stockerPhoto}" mode="advanced" label="Choisissez votre photo" uploadLabel="Archiver" cancelLabel="Annuler" /> </p:tab> </p:tabView> <f:facet name="footer"> Titre : <p:inputText value="#{archivage.nouveauTitre}" /> <p:commandButton icon="ui-icon-document" value="Cration d'un nouveau lot de photos" action="#{archivage.nouveauLot()}" /> </f:facet> </p:panel> </h:form> </h:body> </html>
service.Archivage.java
package service; import entit.*; import java.io.*; import java.util.*; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.faces.application.FacesMessage; import javax.faces.bean.*; import javax.faces.context.FacesContext; import org.primefaces.event.FileUploadEvent; import org.primefaces.event.map.OverlaySelectEvent; import org.primefaces.model.*; import org.primefaces.model.map.*; @ManagedBean @SessionScoped public class Archivage { @EJB private ServiceREST service; private LotDePhotos lot; private List<LotDePhotos> listeDesLots; private String titre; private String nouveauTitre; private MapModel cartographie = new DefaultMapModel(); private String centre; private Marker marqueur; private boolean miseAJour; private String description; @PostConstruct private void init() { File rep = new File(service.getRpertoire()); if (!rep.exists()) rep.mkdir(); listeDesLots = service.getListeDesLots(); if (!listeDesLots.isEmpty()) lot = listeDesLots.get(0);
marqueurs(); } public boolean isMiseAJour() { return miseAJour; } public String getTitre() { return titre; } public void setTitre(String titre) { this.titre = titre; } public String getNouveauTitre() { return nouveauTitre; } public void setNouveauTitre(String nouveauTitre) { this.nouveauTitre = nouveauTitre; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public List<LotDePhotos> getListeDesLots() { return listeDesLots; } public LotDePhotos getLot() { return lot; } public Photo getPhoto(String id) { return service.recherchePhoto(id); } public String getCentre() { return centre; } public MapModel getCartographie() { return cartographie; } public void changeLot() { lot = service.rechercheLot(titre); marqueurs(); miseAJour = true; } public void nouveauLot() { service.crationLotDePhotos(nouveauTitre); listeDesLots = service.getListeDesLots(); } public void changeTitre() { service.modifierTitreLotDePhoto(lot); listeDesLots = service.getListeDesLots(); } public void supprimeLot() { service.supprimeLot(lot.getId()); init(); } public void changeDescription(Photo photo) { service.modifierDescriptionPhoto(photo); } public void supprimePhoto(Photo photo) { service.supprimePhoto(lot, photo); } public void stockerPhoto(FileUploadEvent evt) { try { UploadedFile fichier = evt.getFile(); service.stocker(lot.getId(), description, fichier.getContents()); message(description, "Votre photo vient d'tre stocke dans le service"); lot = service.rechercheLot(titre); } catch (Exception ex) { message("ATTENTION", "Impossible de stocker votre photo dans le service"); } } public void choixMarqueur(OverlaySelectEvent event) { marqueur = (Marker) event.getOverlay(); miseAJour = false; } public Marker getMarqueur() { return marqueur; } private void marqueurs() { for (Photo photo : lot.getPhotos()) { if (photo.isExif()) { LatLng coordonnes = new LatLng(photo.getLatitude(), photo.getLongitude()); marqueur = new Marker(coordonnes, photo.getDescription(), photo.getId()); cartographie.addOverlay(marqueur); centre = photo.getLatitude()+", "+photo.getLongitude(); } } } private void message(String titre, String dtail) { FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(titre, dtail)); } }
service.ServiceREST.java
package service; import com.drew.imaging.ImageMetadataReader; import com.drew.metadata.*; import com.drew.metadata.exif.GpsDirectory; import entit.*; import java.io.*; import java.util.*; import javax.ejb.*; import javax.persistence.*; import javax.ws.rs.*; import javax.ws.rs.core.Response; @Path("/") @Stateless @LocalBean public class ServiceREST { private final String rpertoire = "ArchivagePhotos/"; @PersistenceContext private EntityManager bd; @POST @Path("{titre}") public Response crationLotDePhotos(@PathParam("titre") String titre) { LotDePhotos lot = new LotDePhotos(titre); bd.persist(lot);
Query requte = bd.createNamedQuery("recherche"); requte.setParameter("titre", titre); lot = (LotDePhotos) requte.getSingleResult(); return Response.ok ().header("id", lot.getId()).build(); } @POST @Path("{id}/description={description}") @Consumes("image/jpeg") public void stocker(@PathParam("id") long id, @PathParam("description") String description, InputStream flux) throws Exception { byte[] octets = lireOctets(flux); stocker(id, description, octets); } @PUT @Path("{id}/titre={titre}") public void changeTitre(@PathParam("id") long id, @PathParam("titre") String titre) { LotDePhotos lot = bd.find(LotDePhotos.class, id); lot.setTitre(titre); bd.merge(lot); } @GET @Path("{nomFichier}") @Produces("image/jpeg") public InputStream restituer(@PathParam("nomFichier") String nom) throws FileNotFoundException { return new FileInputStream(rpertoire+nom); } @GET @Produces("application/json") public List<LotDePhotos> getListeDesLots() { return bd.createNamedQuery("toutLesLots").getResultList(); } @DELETE @Path("{id}") public void supprimeLot(@PathParam("id") long id) { LotDePhotos lot = bd.find(LotDePhotos.class, id); for (Photo photo : lot.getPhotos()) new File(rpertoire+photo.getId()).delete(); bd.remove(lot); } public LotDePhotos rechercheLot(String titre) { Query requte = bd.createNamedQuery("recherche"); requte.setParameter("titre", titre); return (LotDePhotos) requte.getSingleResult(); } public Photo recherchePhoto(String id) { return (Photo) bd.find(Photo.class, id); } public void modifierTitreLotDePhoto(LotDePhotos lot) { bd.merge(lot); } public void modifierDescriptionPhoto(Photo photo) { bd.merge(photo); } public void supprimePhoto(LotDePhotos lot, Photo photo) { lot.getPhotos().remove(photo); bd.merge(lot); bd.remove(recherchePhoto(photo.getId())); new File(rpertoire+photo.getId()).delete(); } public String getRpertoire() { return rpertoire; } public void stocker(long id, String description, byte[] octets) throws Exception { Photo photo = new Photo(description); FileOutputStream fichier = new FileOutputStream(rpertoire+photo.getId()); fichier.write(octets); fichier.close(); exif(rpertoire, photo); LotDePhotos lot = bd.find(LotDePhotos.class, id); lot.ajoutPhoto(photo); bd.merge(lot); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } private void exif(String rpertoire, Photo photo) throws Exception { Metadata metadata = ImageMetadataReader.readMetadata (new File(rpertoire+photo.getId())); Directory exif = metadata.getDirectory(GpsDirectory.class); boolean EXIF; try { exif.getDescription(GpsDirectory.TAG_GPS_LATITUDE); EXIF = true; } catch (Exception ex) { EXIF = false; } photo.setExif(EXIF); if (photo.isExif()) { photo.setLatitude(toDouble(exif.getDescription(GpsDirectory.TAG_GPS_LATITUDE))); photo.setLongitude(toDouble(exif.getDescription(GpsDirectory.TAG_GPS_LONGITUDE))); photo.setJour(date(exif.getDescription(GpsDirectory.TAG_GPS_DATE_STAMP))); } } private double toDouble(String localisation) { String[] degrs = localisation.split("");
double valeur = Integer.parseInt(degrs[0]); String[] minutes = degrs[1].split("'"); valeur += Integer.parseInt(minutes[0]) / (double)60; String[] secondes = minutes[1].split("\""); valeur += Double.parseDouble(secondes[0]) / 3600; return valeur; } private Date date(String date) { String[] sparation = date.split(":"); int anne = Integer.parseInt(sparation[0]); int mois = Integer.parseInt(sparation[1]); int jour = Integer.parseInt(sparation[2]); Calendar calendrier = new GregorianCalendar(anne, mois-1, jour); return calendrier.getTime(); } }
entit.LotDePhotos.java
package entit; import java.io.Serializable; import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity @NamedQueries({ @NamedQuery(name="recherche", query="SELECT lot FROM LotDePhotos lot WHERE lot.titre = :titre"), @NamedQuery(name="toutLesLots", query="SELECT lot FROM LotDePhotos lot") }) public class LotDePhotos implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String titre; @JoinColumn @OneToMany(fetch= FetchType.EAGER, cascade= CascadeType.ALL) private List<Photo> photos = new ArrayList<Photo>(); public LotDePhotos() { } public LotDePhotos(String titre) { this.titre = titre; } public long getId() { return id; } public String getTitre() { return titre; } public void setTitre(String titre) { this.titre = titre; } public List<Photo> getPhotos() { return photos; } public void ajoutPhoto(Photo photo) { photos.add(photo); } @Override public String toString() { return titre; } }
entit.Photo.java
package entit; import java.io.Serializable; import java.util.Date; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @Entity public class Photo implements Serializable { @Id private String id; private String description; private boolean exif; private double longitude; private double latitude; @Temporal(javax.persistence.TemporalType.DATE) private Date jour; public Photo() { id = System.currentTimeMillis()+".jpg"; } public Photo(String description) { id = System.currentTimeMillis()+".jpg"; this.description = description; } public String getId() { return id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Date getJour() { return jour; } public void setJour(Date jour) { this.jour = jour; } public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } public boolean isExif() { return exif; } public void setExif(boolean localisation) { this.exif = localisation; } public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } @Override
Principal et rle
Les principaux et les rles tiennent une place importante dans la scurit logicielle. Un principal est un utilisateur qui a t authentifi (par un nom et un mot de passe stocks dans une base de donnes ou dans un fichier spcifique dans le serveur d'application, par exemple). Les principaux peuvent tre organises en groupes, appels rles, qui leur permettent de partager un ensemble de permissions (accs au systme de facturation ou possibilit d'envoyer des messages dans un workflow, par exemple). Comme vous pouvez le constater, un utilisateur authentifi est li un principal qui possde un identifiant unique et qui peut tre associ plusieurs rles. Ainsi le principal de l'utilisateur Jeanne, par exemple, est li aux rles de consultation, de cration et de modification.
Authentification et habilitation
La scurisation d'une application implique deux fonctions : l'authentification et l'habilitation. La premire consiste vrifier l'identit de l'utilisateur (son identifiant et son mot de passe, son empreinte biomtrique, etc.) en utilisant son systme d'authentification et en affectant un principal cet utilisateur. L'habilitation consiste dterminer si un principal (un utilisateur authentifi) a accs une ressource particulire (une photo, par exemple) ou une fonction donne (supprimer une photo, par exemple). Selon son rle, l'utilisateur peut avoir accs toutes les ressources, aucune ou certaines d'entre elles. Dans un scnario de scurit classique, l'utilisateur doit entrer son identifiant et son mot de passe via une interface client (web ou Swing). Ces informations sont vrifies avec JAAS (Java Authentication and Authorization Service) via un systme d'authentification sous-jacent. Si l'authentification russit, l'utilisateur est associ un principal qui est ensuite lui-mme associ un ou plusieurs rles. Lorsque l'utilisateur accde un EJB scuris (ou un composant REST scuris), le principal est transmis de faon transparente l'EJB, qui l'utilise pour savoir si le rle de l'appelant l'autorise accder aux mthodes qu'il tente d'excuter.
La scurit de java EE repose largement sur l'API JAAS qui est automatiquement intgre dans les serveurs d'applications certifis Java EE. En fait, JAAS est l'API utilise en interne par les couches web et EJB pour raliser des oprations d'authentification et d'habilitation. Elle peut accder galement aux systmes d'authentification sous-jacents comme LDAP, Active Directory, etc.
alors une bote de dialogue ou un formulaire d'authentification afin de demander les informations l'utilisateur. Si l'utilisateur est prcdemment authentifi, le navigateur utilise alors le cache local possdant les renseignements d'une page prcdente.
Le serveur Java EE Glassfish dispose d'un mcanisme intgr trs souple pour grer les Realms. Il est possible d'utiliser le fichier domain.xml, le fichier de configuration spcifique au domaine, une base de donnes ou encore les donnes d'un serveur LDAP. Les rgles d'utilisation des Realms sont les suivantes : Tout utilisateur peut tre membre d'aucun ou plusieurs rles. Tous les rles peuvent contenir aucun ou plusieurs utilisateurs. Les Realms permettent de dfinir les utilisateurs qui peuvent accder des ressources en fournissant la liste d'un ou plusieurs rles disposant d'un droit d'accs. Une authentification base sur les Realms, galement appele security policy ou scurit de domaine, permet de dfinir la politique de scurit pour l'accs aux ressources. Le serveur Glassfish est livr avec trois Realms pr-configurs file, certificate et admin-realm. Deux sont bass sur des fichiers et un autre sur un certificat. Si une application ne prcise pas de Realm elle utilise le Realm file par dfaut.
La commande suivante liste tous les utilisateurs contenus dans le Realm de type file :
$ asadmin list-file-users
admin-realm Ce type de Realm est galement un Realm de type file mais sauvegarde les comptes administrateurs dans le fichier domains/domain1/config/admin-keyfile. certificat L'authentification est alors base sur les certificats clients. Le serveur utilise des comptes dans une base de donnes certifie. Avec ce type de scurit, le serveur utilise les certificats avec le protocole HTTPS pour l'authentification des clients. Glassfish utilise le format Java Key Store et stocke sa cl prive et ses certificats dans les fichiers domains/domain1/config/kestore.jsk et domains/domain1/config/cacerts.jsk. Les fichiers Java Key Store peuvent tre grs en utilisant l'outil Java SE keytool. ldap Ce type de Realm permet d'utiliser un annuaire LDAP pour les authentifications utilisateurs. jdbc Les Realms JDBC permettent de stocker les comptes utilisateurs dans une base de donnes relationnelle. Les Realms JDBC utilisent une ressource JDBC pour se connecter la base de donnes contenant les comptes utilisateurs.
La mise en place d'un Realm JBDC repose sur des paramtres JAAS. Ainsi, dans le paramtre JAAS context nous devons spcifier le nom de la classe pour le Realm JDBC (jdbcRealm). La proprit Digest Algorithm est ncessaire pour la gestion du mot de passe. L'algorithme de cryptage MD5 est souvent utilis pour spcifier le cryptage des mots de passe. Le paramtre Assign Group est utilis pour spcifier un groupe pour tous les utilisateurs.
custom Ce type trs souple permet de dfinir notre propre implmentation du Realm.
Authentification de base Le schma d'authentification HTTP le plus simple se nomme autorisation de base (Basic Authorization). Ce mcanisme consiste utiliser un nom utilisateur/identifiant et un mot de passe en clair. Authentification par digest Avec ce type d'autorisation, l'utilisateur saisie son mot de passe en clair, mais celui-ci est envoy sous forme cripte partir d'une chane de caractres sur le rseau. Cette authentification est appele authentification par Digest (Digest Auth) et utilise l'algorithme de hachage unilatral SHA ou MD5. Authentification par certificat Avec ce type d'authentification, l'utilisateur doit possder un certificat client pour accder au serveur. Cette approche est la plus sre puisque les certificats sont grs de faon centralise par des autorits spcialises.
DIGEST L'authentification est proche de la prcdente mais le mot de passe client est crypt avant l'envoi par le navigateur. FORM L'authentification est base sur un formulaire de saisie de type identifiant / mot de passe personnalisable et dvelopp en XHTML, supportant les protocoles HTTP et HTTPS. CLIENT-CERT L'authentification est base sur un certificat cl publique. La communication est effectue au travers des protocoles HTTP et HTTPS.
service.Archivage.java
package service; import java.io.*; import javax.annotation.PostConstruct; import javax.ws.rs.*; @Path("/") public class Archivage { private final String rpertoire = "ArchivagePhotos/"; @PostConstruct private void init() { File rep = new File(rpertoire); if (!rep.exists()) rep.mkdir(); } @GET @Path("consultation/liste") @Produces("application/json") public String[] getPhotos() { return new File(rpertoire).list(); } @GET @Path("consultation/{nomFichier}") @Produces("image/jpeg") public InputStream restituer(@PathParam("nomFichier") String nom) throws FileNotFoundException {
return new FileInputStream(rpertoire+nom); } @POST @Path("gestion/{nomFichier}") @Consumes("image/jpeg") public void stocker(@PathParam("nomFichier") String nom, InputStream flux) throws IOException { byte[] octets = lireOctets(flux); FileOutputStream fichier = new FileOutputStream(rpertoire+nom); fichier.write(octets); fichier.close(); } @PUT @Path("gestion/ancien={ancien}/nouveau={nouveau}") public void changerNomPhoto(@PathParam("ancien") String ancien, @PathParam("nouveau") String nouveau) { File rep = new File(rpertoire); File ancienFichier = new File(rep+"/"+ancien); File nouveauFichier = new File(rep+"/"+nouveau); ancienFichier.renameTo(nouveauFichier); System.out.println(ancienFichier.getPath()); System.out.println(nouveauFichier.getPath()); } @DELETE @Path("gestion/{nomFichier}") public void supprimer(@PathParam("nomFichier") String nom) { new File(rpertoire+nom).delete(); } private byte[] lireOctets(InputStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int octetsLus = 0; do { octetsLus = stream.read(buffer); if (octetsLus > 0) { baos.write(buffer, 0, octetsLus); } } while (octetsLus > -1); return baos.toByteArray(); } }
glassfish-web.xml
<?xml version="1.0" encoding="UTF-8"?> <sun-web-app error-url=""> <context-root>/Photos</context-root> <security-role-mapping> <role-name>gestion</role-name> <group-name>creation</group-name> <group-name>modification</group-name> </security-role-mapping> <security-role-mapping> <role-name>recuperer</role-name> <group-name>consultation</group-name> </security-role-mapping> <security-role-mapping> <role-name>supprimer</role-name> <principal-name>manu</principal-name> </security-role-mapping> </sun-web-app> Quand nous avons cr photos-realm, nous avons dfini un certain nombre d'utilisateurs associs un ensemble de groupes, respectivement, pour notre exemple, les utilisateurs manu, jeanne et martin avec les groupes consultation, creation, modification et suppression. Cette notion de groupe est souvent associe la notion de rle. Toutefois, il est possible de faire la distinction pour une application donne. C'est d'ailleurs souvent le concept qui est retenu dans le systme de scurit des serveurs d'applications implmentant Java EE. C'est dans cette dmarche que le descripteur de dploiement spcifique au serveur glassfish-web.xml est conu. Il dfinit les rles attendus cette application Web et les associent des utilisateurs ou des groupes d'utilisateurs. Ainsi, dans notre exemple, nous devons prendre en compte trois rles spcifiques, gestion, recuperer et supprimer. Puisque nous avons trois cas particulier, nous devons donc proposer trois balises <security-role-mapping> qui vont raliser le mapping entre les rles de l'application dfinis par les balises <role-name> et les utilisateurs ou les groupes au travers respectivement des balises <principal-name> et <group-name>. Une fois que nous connaissons les diffrents rles ncessaires, nous pouvons grer de faon prcise la scurit associe cette application web dans le descripteur de dploiement WEB-INF/web.xml. Trois parties sont ncessaires pour exploiter pleinement la scurit, le mode d'authentification au travers de la balise <login-config>, tous les rles utiliss par cette
application avec la balise <security-role> et enfin les contraintes d'utilisation pour chaque rle au travers de la balise <security-constraint>.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>Gestion des photos</web-resource-name> <url-pattern>/gestion/*</url-pattern> <http-method>POST</http-method> <http-method>PUT</http-method> </web-resource-collection> <auth-constraint> <role-name>gestion</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Suppression de photos</web-resource-name> <url-pattern>/gestion/*</url-pattern> <http-method>DELETE</http-method> </web-resource-collection> <auth-constraint> <role-name>supprimer</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Rcupration de photo</web-resource-name> <url-pattern>/consultation/*</url-pattern> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <role-name>recuperer</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>gestion</role-name> <role-name>recuperer</role-name> <role-name>supprimer</role-name> </security-role> <login-config> <auth-method>BASIC</auth-method> <realm-name>photos-realm</realm-name> </login-config> </web-app> Ainsi, l'aide de la balise <login-config>, nous pouvons spcifier le realm prendre en compte avec la balise <realm-name> et de prciser le type d'authentification attendu l'aide de la balise <auth-method>, ici le mode basique. Nous dfinissons ensuite tous les rles prendre en compte au travers de la balise <security-role> l'intrieur de laquelle va se trouver l'ensemble des balises <rolename>. La grosse partie se situe dans la dfinissions des contraintes de scurit associes chaque rle spcifi, au travers de la balise <security-constraint>. A l'intrieur de cette balise se trouvent un certain nombre d'autres balises qui vont nous permettre de rgler prcisment les filtres d'utilisation pour chacun de ces rles. <web-resource-collection> : cette balise permet de dcrire les contarintes d'accs aux ressources spcifies. <web-resource-name> : cette balise permet de donner un nom au systme d'authentification. <url-pattern> : cette balise permet de dfinir, partir d'une expression rgulire, les ressources protger. Il doit y avoir zro ou plusieurs balises <url-pattern> dans une balise <web-ressource-collection>. L'abscence de la balise <url-pattern> indique que la contrainte de scurit doit s'appliquer toutes les ressources. <http-method> : il est possible de proposer un filtre sur le choix des mthodes HTTP. L aussi, cette balise est optionnelle et nous pouvons en avoir plusieurs. <auth-constraint> : cette balise contient zro ou plusieurs balises <role-name> afin de prciser le ou les rles ayant accs aux ressources. <role-name> : cette balise permet de dfinir le nom du rle ayant accs aux ressources scurises.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.photos" android:versionCode="1" android:versionName="1.0"> <application android:label="Stockage photos" > <activity android:name="ArchivagePhotos" android:label="Stocker vos photos" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="ChoixServeur" android:label="Choix du serveur" android:theme="@android:style/Theme.Dialog" /> </application> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
res/layout/adresse.xml
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="3dp"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="3dp"> <Button android:id="@+id/ok" android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="OK" android:onClick="ok"/> <EditText android:layout_toLeftOf="@id/ok" android:id="@+id/adresse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Adresse IP" /> </RelativeLayout> <EditText android:id="@+id/utilisateur" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Utilisateur" /> <EditText android:id="@+id/motdepasse" android:layout_width="fill_parent" android:layout_height="wrap_content" android:password="true" android:hint="Mot de passe" /> </LinearLayout>
fr.btsiris.rest.ChoixServeur.java
package fr.btsiris.rest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.*; import android.widget.EditText; public class ChoixServeur extends Activity { private EditText adresse; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.adresse); adresse = (EditText) findViewById(R.id .adresse); } public void ok(View vue) { Intent intent = new Intent(); intent.putExtra("adresse", adresse.getText().toString()); setResult(RESULT_OK, intent); finish(); } }
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#008800" android:padding="3dp"> <EditText android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Description de la photo" android:layout_alignParentBottom="true"/> <LinearLayout android:id="@+id/boutons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@id/description"> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Photographier" android:onClick="photographier" /> <Button android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="Envoyer" android:onClick="envoyer" /> </LinearLayout> <ImageView android:id="@+id/image" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:layout_above="@id/boutons" /> </RelativeLayout>
fr.btsiris.rest.ArchivagePhotos.java
package fr.btsiris.photos; import android.app.Activity; import android.content.Intent; import android.graphics.*; import android.net.Uri; import android.os.*; import android.provider.MediaStore; import android.view.View; import android.widget.*; import java.io.*; import org.apache.http.auth.*; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.FileEntity; import org.apache.http.impl.client.DefaultHttpClient; public class ArchivagePhotos extends Activity { private EditText description; private ImageView image; private Uri fichierUri; private String adresse, utilisateur, motdepasse; private final int RECHERCHE_ADRESSE = 1; private final int PHOTOGRAPHIER = 2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); description = (EditText) findViewById(R.id .description); image = (ImageView) findViewById(R.id .image); startActivityForResult(new Intent(this, ChoixServeur.class), RECHERCHE_ADRESSE); }
@Override protected void onActivityResult(int requestCode, int resultCode, Intent intention) { switch (requestCode) { case RECHERCHE_ADRESSE : adresse = intention.getStringExtra("adresse"); utilisateur = intention.getStringExtra("utilisateur"); motdepasse = intention.getStringExtra("motdepasse"); break; case PHOTOGRAPHIER : File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); Bitmap photo = BitmapFactory.decodeFile(fichier.getPath()); image.setImageBitmap(photo); break; } } public void photographier(View vue) { Intent intention = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File fichier = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); fichierUri = Uri.fromFile(fichier); intention.putExtra(MediaStore.EXTRA_OUTPUT, fichierUri); startActivityForResult(intention, PHOTOGRAPHIER); } public void envoyer(View vue) throws IOException { DefaultHttpClient client = new DefaultHttpClient(); client.getCredentialsProvider().setCredentials( new AuthScope(adresse, 8080), new UsernamePasswordCredentials(utilisateur, motdepasse)); HttpPost requete = new HttpPost("http://"+adresse+":8080/Photos/gestion/"+description.getText().toString()+".jpg"); requete.setEntity(new FileEntity(new File(Environment.getExternalStorageDirectory(), "photo.jpg"), "image/jpeg")); client.execute(requete); } }
Le serveur GlassFish supporte les protocoles SSL et TLS pour les changes scuriss. Pour utiliser SSL, GlassFish doit avoir un certificat pour chaque interface externe ou adresse IP acceptant les connexions scurises. Par dfaut, HTTPS est activ sur le port 8181 pour le trafic Web, et SSL est activ sur les ports 3820 et 3902 pour le trafic IIOP.