Services Web et JORAM
Transcription
Services Web et JORAM
Services Web et JORAM Utilisation de JORAM comme MOM par des Services Web TEMPLIER Thierry Draft Draft Services Web et JORAM: Utilisation de JORAM comme MOM par des Services Web TEMPLIER Thierry Publié 12/11/2003 Copyright © 2003 TEMPLIER Thierry Ce document a pour but de décrire la mise en place de communications asynchrones entre services web en tant que services simples avec comme couche de transport, JMS. Draft Draft Draft Draft Table des matières I. Mise en place .......................................................................................................................... 1 1. Joram ............................................................................................................................ 3 1.1. Partie cliente ........................................................................................................ 3 1.2. Partie serveur ....................................................................................................... 4 1.3. Création des queues ............................................................................................... 5 2. Axis .............................................................................................................................. 8 3. WSIF ............................................................................................................................ 9 II. SOAP over JMS ...................................................................................................................... 10 4. Côté serveur ( AXIS ) ....................................................................................................... 12 4.1. Listener simple ..................................................................................................... 12 4.2. Listener de la distribution ....................................................................................... 15 5. Côté client ...................................................................................................................... 19 5.1. Axis .................................................................................................................... 19 5.2. Axis ( classes de la distribution ) .............................................................................. 22 5.2.1. Via le transport JMS .................................................................................... 22 5.2.2. Via l'url JMS .............................................................................................. 23 5.3. WSIF .................................................................................................................. 25 5.3.1. Appel synchrone ......................................................................................... 28 5.3.2. Appel asynchrone ....................................................................................... 30 6. Patcher Axis et WSIF ....................................................................................................... 32 6.1. Patcher Axis ......................................................................................................... 32 6.2. Patcher WSIF ....................................................................................................... 32 6.2.1. Envoi des messages ..................................................................................... 32 6.2.2. Réception des messages ( synchrone ) ............................................................. 33 6.2.3. Réception des messages ( asynchrone ) ........................................................... 34 Bibliographie ............................................................................................................................. 38 iv Draft Draft Liste des illustrations 4.1. Schéma d'architecture côté serveur ........................................................................................... 16 5.1. Schéma d'architecture côté client avec Axis ................................................................................ 19 5.2. Schéma d'architecture côté client avec WSIF .............................................................................. 25 v vi Draft Draft I. Mise en place Cette partie décrit les procédures d'installation et de mise en oeuvre des différentes applications et outils qui vont être utilisés durant cette étude. Ces outils se classent en trois grandes parties: • Applications orientée messages applicatifs ( ou MOM ). • Moteurs SOAP. • Frameworks d'invocation de Services Web. Draft Draft Table des matières 1. Joram .................................................................................................................................... 3 1.1. Partie cliente ........................................................................................................ 3 1.2. Partie serveur ....................................................................................................... 4 1.3. Création des queues ............................................................................................... 5 2. Axis ...................................................................................................................................... 8 3. WSIF .................................................................................................................................... 9 ii Draft Draft 1. Joram Joram est une implémentation http://joram.objectweb.org/. open-source de JMS. Joram est téléchargeable à l'url suivante: Pour installer Joram, il suffit de télécharger la distribution et de la décompresser. Les bibliothèques Java nécessaires se trouvent dans le sous répertoire lib. 1.1. Partie cliente Pour la partie client, les fichiers jar suivants sont nécessaires: • JCup.jar • jms.jar • jndi.jar • joram.jar • jta.jar • log4j.jar • ow_monolog.jar Pour accéder au serveur JMS, il faut utiliser JNDI et donc positionner paramètres. Cela est possible soit via le fichier jndi.properties ( accessible via le classpath ), soit via la ligne de commande ( -D ), soit en paramètres de la classe InitialContext ( avec une Hashtable ). Ces paramètres sont les suivants ( jndi.properties ) : java.naming.factory.initial fr.dyade.aaa.jndi2.client.NamingContextFactory java.naming.factory.host localhost java.naming.factory.port 16400 Note Le nom de machine et le port dépendent de la machine et du port de démarrage du serveur JMS. Il est également nécessaire de configurer les paramètres de débugage de la partie cliente de Joram. Il s'agit en fait de la configuration de log4j. Ces paramètres sont les suivants ( a3debug.cfg ) : ############# # Appenders # ############# # # # # Use A3Log4jXAppender permits to redirect output of each AgentServer in a different file named server#{$id}.audit where $id is the AgentServer id, so all servers can run in the same directory. Be careful, using classic log4j FileAppender requires to launch each server in a different directory. ## A3Log4jFileAppender, for example: # log4j.appender.local=fr.dyade.aaa.util.A3Log4jFileAppender ## A3Log4jRollingFileAppender, for example: 3 Draft Joram Draft log4j.appender.local=fr.dyade.aaa.util.A3Log4jRollingFileAppender ## Roll over the file each time it reaches 5MB. log4j.appender.local.MaxFileSize=5000KB ## Keep ten backup file log4j.appender.local.MaxBackupIndex=2 ## DailyRollingFileAppender, for example: # log4j.appender.local=fr.dyade.aaa.util.A3Log4jDailyRollingFileAppender # log4j.appender.local.DatePattern='.'yyyy-MM-dd-HH log4j.appender.local.layout=org.apache.log4j.PatternLayout log4j.appender.local.layout.ConversionPattern=[%t] [%d{ISO8601}] %-5p %x - %m%n # tty is set to be a ConsoleAppender which outputs to System.out. log4j.appender.tty=org.apache.log4j.ConsoleAppender log4j.appender.tty.layout=org.apache.log4j.PatternLayout log4j.appender.tty.layout.ConversionPattern=[%t] [%d{ISO8601}] %-5p %x - %m%n log4j.appender.tty.Threshold=DEBUG ######################### # Factory of Categories # ######################### log4j.loggerFactory= org.objectweb.util.monolog.wrapper.log4j.MonologCategoryFactory ######################## # Configure Categories # ######################## log4j.rootLogger=ERROR, tty # log4j.logger.fr.dyade.aaa.agent.Agent=DEBUG # log4j.logger.fr.dyade.aaa.agent.Engine=DEBUG # log4j.logger.fr.dyade.aaa.agent.Service=DEBUG # log4j.logger.fr.dyade.aaa.mom.Destination=DEBUG # log4j.logger.fr.dyade.aaa.mom.Proxy=DEBUG # log4j.logger.fr.dyade.aaa.jndi2.client=DEBUG # log4j.logger.fr.dyade.aaa.jndi2.server=DEBUG # log4j.logger.fr.dyade.aaa.joram.Client=DEBUG 1.2. Partie serveur Pour la partie serveur, les fichiers jar suivants sont nécessaires: • jakarta-regexp-1.2.jar • JCup.jar • log4j.jar • mom.jar • ow_monolog.jar • xerces.jar Pour lancer le serveur, il faut configurer les services que l'on veut que le serveur lance. Ces services sont configurés dans le fichier a3servers.xml. Ce fichier doit être dans le classpath pour être pris en compte ainsi que le fichier de débugage vu dans la partie cliente. 4 Draft Joram Draft <?xml version="1.0"?> <config> <domain name="D1"/> <server id="0" name="S0" hostname="localhost"> <network domain="D1" port="16301"/> <service class="fr.dyade.aaa.ns.NameService"/> <service class="fr.dyade.aaa.mom.dest.AdminTopic"/> <service class="fr.dyade.aaa.mom.proxies.tcp.ConnectionFactory" args="16010 root root"/> <service class="fr.dyade.aaa.jndi2.server.JndiServer" args="16400"/> </server> </config> Service de nommage. Service d'administration. Service de récupération de connexions. Service du serveur JNDI. Le script suivant permet de lancer un serveur JMS sans persistence de messages. set JAVA_HOME=d:\jdk1.3.1_01 set set set set set set set set set set JORAM_HOME=c:\ws_joram CLASSP=%JORAM_HOME%\lib\jakarta-regexp-1.2.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\JCup.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\log4j.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\mom.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ow_monolog.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jaxp.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\xerces.jar CONFIG=%JORAM_HOME%\conf\properties CONFIG=%CONFIG%;%JORAM_HOME%\conf\xml %JAVA_HOME%\bin\java -DTransaction=fr.dyade.aaa.util.NullTransaction -classpath %CLASSP%;%CONFIG% fr.dyade.aaa.agent.AgentServer %1 %2 Pour le lancer, il suffit de lancer le script avec les paramètres suivants: > runJoram 0 ./s0 1.3. Création des queues Pour pouvoir utiliser Joram, il faut créer les objets ( ConnectionFactory, QueueConnectionFactory et Queue ) nécessaires via une classe. La classe suivante est extraite des exemples de la distribution de Joram 3.6.0: package org.joram.classic; import fr.dyade.aaa.joram.admin.*; public class ClassicAdmin { 5 Draft Joram Draft public static void main(String[] args) throws Exception { System.out.println(); System.out.println("Classic administration..."); AdminItf admin = new fr.dyade.aaa.joram.admin.AdminImpl(); admin.connect("root", "root", 60); javax.jms.Queue requestQueue = admin.createQueue(0); javax.jms.ConnectionFactory cf = admin.createConnectionFactory("localhost", 16010); javax.jms.QueueConnectionFactory qcf = admin.createQueueConnectionFactory("localhost", 16010); User user = admin.createUser("anonymous", "anonymous", 0); admin.setFreeReading(requestQueue); admin.setFreeWriting(requestQueue); javax.naming.Context jndiCtx = new javax.naming.InitialContext(); jndiCtx.bind("cf", cf); jndiCtx.bind("qcf", qcf); jndiCtx.bind("JMS_RequestQueue", requestQueue); jndiCtx.close(); admin.disconnect(); System.out.println("Admin closed."); } } Connexion au service d'administration Creation des objets pour utiliser Joram Ajout des objets dans le contexte jndi de Joram Deconnexion au service d'administration Attention Frédéric Maistre nous a signalé un bug sur la fermeture des sockets sous Linux avec un jdk1.3, ce qui fait que le code précédent se bloque sur la ligne "admin.disconnect()". Un moyen de contourner le problème est décrit à l'url suivante: http://www.objectweb.org/wws/arc/joram/2002-04/msg00012.html. Voici le fichier de lancement de l'administration de Joram ( adminJoram.bat ) : set JAVA_HOME=d:\jdk1.3.1_01 set set set set set set set set set set set JORAM_HOME=c:\ws_joram CLASSP=%JORAM_HOME\lib\jakarta-regexp-1.2.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\JCup.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jms.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\joram.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jta.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\log4j.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ow_monolog.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ws_joram.jar CONFIG=%JORAM_HOME%\conf\properties CONFIG=%CONFIG%;%JORAM_HOME%\conf\xml 6 Draft Joram %JAVA_HOME%\bin\java -classpath %CLASSP%;%CONFIG% org.joram.classic.ClassicAdmin 7 Draft Draft Draft 2. Axis Axis est une implémentation open-source de SOAP. Axis est téléchargeable à l'url suivante: http://ws.apache.org/axis/. Axis comprend à la fois la partie serveur qui peut être accéder via HTTP ou JMS, ainsi que la partie cliente d'accès aux services Web. Pour installer Axis, il suffit de télécharger la distribution et de la décompresser. Les bibliothèques Java nécessaires se trouvent dans le sous répertoire lib. Pour utiliser Axis, il est nécessaire d'avoir les fichiers jar suivants dans le classpath: • saaj.jar • log4j.jar • jaxrpc.jar • commons-discovery.jar • axis.jar • wsdl4j.jar • commons-logging.jar Axis permet d'ajouter des éléments dans la chaîne de traitement d'un service web au moyen d'handlers. Ceci est particulièrement utile pour gérer la sécurité, l'authentification ainsi que des couches de transport de SOAP autres que HTTP. Ces handlers peuvent être utilisés aussi bien côté serveur que côté serveur ( en entrée et/ou en sortie ). 8 Draft Draft 3. WSIF WSIF est une implémentation open-source d'invocation de services web se basant sur WSDL, le langage de description des services web. WSIF est téléchargeable à l'url suivante: http://ws.apache.org/wsif/. WSIF comprend uniquement la partie cliente d'accès aux services Web. Il s'agit d'une sur-couche des frameworks d'invocation SOAP tels qu'Axis ou Apache SOAP, mais il définit également ses propres providers d'invocation ( SOAP over JMS, JMS natif ). Pour installer Axis, il suffit de télécharger la distribution et de la décompresser. Les bibliothèques Java nécessaires se trouvent dans le sous répertoire lib. Pour utiliser Axis, il est nécessaire d'avoir les fichiers jar suivants dans le classpath: • wsif.jar ainsi que les fichiers jar de Axis si on utilise Axis 9 Draft Draft II. SOAP over JMS Draft Draft Table des matières 4. Côté serveur ( AXIS ) ............................................................................................................... 12 4.1. Listener simple ..................................................................................................... 12 4.2. Listener de la distribution ....................................................................................... 15 5. Côté client .............................................................................................................................. 19 5.1. Axis .................................................................................................................... 19 5.2. Axis ( classes de la distribution ) .............................................................................. 22 5.2.1. Via le transport JMS .................................................................................... 22 5.2.2. Via l'url JMS .............................................................................................. 23 5.3. WSIF .................................................................................................................. 25 5.3.1. Appel synchrone ......................................................................................... 28 5.3.2. Appel asynchrone ....................................................................................... 30 6. Patcher Axis et WSIF ............................................................................................................... 32 6.1. Patcher Axis ......................................................................................................... 32 6.2. Patcher WSIF ....................................................................................................... 32 6.2.1. Envoi des messages ..................................................................................... 32 6.2.2. Réception des messages ( synchrone ) ............................................................. 33 6.2.3. Réception des messages ( asynchrone ) ........................................................... 34 xi Draft Draft 4. Côté serveur ( AXIS ) Côté serveur, il faut un listener JMS qui est à l'écoute d'une queue JMS et qui appelle le moteur SOAP d'Axis, comme Tomcat le fait au moyen d'une application web ( webapp ). Pour bien comprendre le mécanisme, dans un premier temps, nous allons étudier un listener d'invocation simple et dans un second temps, le listener fournit par la distribution d'Axis. Nous allons configurer un service simple qui renvoie un nombre à partir d'une chaîne de caractère. Le moteur SOAP d'Axis se base sur le fichier server-config.wsdd pour configurer les services web accessibles: <?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"> <service name="PriceService" provider="java:RPC"> <namespace> http://xml.apache.org/axis/wsif/samples/jms/ServiceAvailability </namespace> <parameter value="org.axis.PriceService" name="className"/> <parameter value="getPrice" name="allowedMethods"/> <wsdlFile>c:/ws_joram/conf/wsdl/GetPrice.wsdl</wsdlFile> </service> </deployment> Définition du service. Ce service est de type "java:RPC". Il s'agit donc d'un appel distant à une méthode. Il aurait également pu être de type "java:MSG". Définition du namespace pour l'invocation du service. Définition de la classe du service. Définition des méthodes pouvant être appelées pour la classe du service ( méthodes exposées ). Définition du fichier wsdl décrivant le service. 4.1. Listener simple La classe du listener comprend plusieurs parties: • L'initialisation: cette partie récupére les objets nécessaires pour se connecter à la queue de réception ainsi que définir la classe comme listener de la queue de réception. public static final InitialContext getInitialContext() throws javax.naming.NamingException { InitialContext ictx=new javax.naming.InitialContext(); return ictx; } private void initialize() throws AxisFault { try { InitialContext ictx=getInitialContext(); queueConnFactory= (QueueConnectionFactory)ictx.lookup(JMS_FACTORY); // Look up the Request Queue 12 Draft Côté serveur ( AXIS ) Draft queue=(Queue)ictx.lookup(REQUEST_QUEUE); queueConn=queueConnFactory.createQueueConnection(); // Create a session (non-transactional) that // automatically acknowledges message receipt. session=queueConn.createQueueSession( false,Session.AUTO_ACKNOWLEDGE); receiver=session.createReceiver(queue); queueConn.start(); System.out.println("En attente...\n"); receiver.setMessageListener(this); } catch (Exception e) { throw AxisFault.makeFault(e); } } Récupération de la factory des connexions aux queues JMS. Récupération de la queue de réception des messages SOAP. Connexion sur la queue de réception. Positionnement de la classe comme listener de la queue JMS. • La récupération du moteur SOAP d'Axis: l'instanciation de celui-ci se base sur le fichier de configuration serveur-config.wsdd. Il est chargé une seule fois et réutilisé par la suite pour gagner du temps. public AxisEngine getAxisEngine() throws Exception { if (engine != null) { return engine; } FileProvider provider = new FileProvider(configuration); return new AxisServer(provider); } • Le listener en lui-même: il reçoit les messages SOAP de la queue et les fait suivre au moteur SOAP d'Axis, récupère la réponse de celui-ci et la renvoie sur la queue temporaire spécifiée ( par la méthode getReplyTo ) en n'oubliant pas de positionner correctement la corrélation du message. private String getMessage(BytesMessage message) { InputStream in = null; try { // get the incoming msg content into a byte array byte[] buffer = new byte[8 * 1024]; ByteArrayOutputStream out = new ByteArrayOutputStream(); for(int bytesRead = message.readBytes(buffer); bytesRead != -1; bytesRead = message.readBytes(buffer)) { out.write(buffer, 0, bytesRead); } return out.toString(); } catch(Exception e) { e.printStackTrace(); return null; } } 13 Draft Côté serveur ( AXIS ) Draft Note Cette méthode est utile pour récupérer le message contenu dans un objet BytesMessage sous de forme de chaîne de caractères. public void onMessage(javax.jms.Message msg) { System.out.println("onMessage event Executed"); quit = true; MessageContext msgContext = null; org.apache.axis.Message responseMsg = null; try { String msgTxt = null; BytesMessage jmsMsg = (BytesMessage) msg; String textJmsMsg=getMessage(jmsMsg); System.out.println(textJmsMsg); Queue replyQueue = (Queue) msg.getJMSReplyTo(); System.out.println("replyQueue = "+replyQueue); sender = session.createSender(replyQueue); // Create an instance of AxisServer from configuration file AxisEngine engine = getAxisEngine(); // Create a MessageContext and associate with the engine msgContext = new MessageContext(engine); // Wrap the incoming TextMessage into Axis Message org.apache.axis.Message soapMessage = new org.apache.axis.Message(textJmsMsg); System.out.println("JMS-SOAP Request:"); System.out.println(textJmsMsg + "\n"); // Set the request message in MessageContext msgContext.setRequestMessage(soapMessage); // Invoke AxisEngine (AxisServer) engine.invoke(msgContext); System.out.println("---Getting response message from Price WebService"); // Get the response message from the MessageContext responseMsg = msgContext.getResponseMessage(); // Get a String representation of response SOAPEnvelope envelope = responseMsg.getSOAPEnvelope(); Element envElement = envelope.getAsDOM(); String strSOAPBody = XMLUtils.ElementToString(envElement); System.out.println("JMS-SOAP Response:"); System.out.println(strSOAPBody); // Create an instance of TextMessage to wrap the SOAP (response) BytesMessage jmsResponseMsg = session.createBytesMessage(); jmsResponseMsg.setJMSCorrelationID(msg.getJMSMessageID()); jmsResponseMsg.writeBytes(strSOAPBody.getBytes()); // Put the response into the queue: 'replyQueue' System.out.println("\n---Sending JMS-SOAP Response 14 Draft Côté serveur ( AXIS ) Draft to the Response Queue"); sender.send(jmsResponseMsg); sender.close(); System.out.println("Waiting for Next JMS-SOAP Requests"); } catch (Exception e) { ex.printStackTrace(); } } Extraction du message sous forme de texte contenu dans le message JMS. Récupération de la queue de réponse du message. Récupération du moteur SOAP d'Axis pour traiter le message. Traitement du message par le moteur SOAP. Récupération de l'enveloppe du message de retour. Conversion de l'enveloppe sous forme de chaîne de caractères. Positionnement de la corrélation du message de retour avec l'identifiant du message reçu. Ceci est indispensable pour que WSIF fasse la correspondance entre les deux messages. Ecriture de l'enveloppe dans le corps du message JMS. Envoi de la réponse. Fermeture de la connexion à la queue de réponse. Attention Il est nécessaire de positionner l'identifiant de corrélation de la demande sur la réponse pour que WSIF sache à quelle demande, il doit associer la réponse. Pour lancer ce listener, il suffit d'utiliser le script axisl.bat : set JAVA_HOME=d:\jdk1.3.1_01 set set set set set set set set set set set set set set set set set set JORAM_HOME=c:\ws_joram CLASSP=%JORAM_HOME\lib\jakarta-regexp-1.2.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\axis.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\soap.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\commons-logging.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\commons-discovery.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jaxrpc.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\saaj.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jaxp.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\xerces.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\JCup.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jms.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\joram.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jta.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\log4j.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ow_monolog.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ws_joram.jar CONFIG=%JORAM_HOME%\conf\properties %JAVA_HOME%\bin\java -classpath %CLASSP%;%CONFIG% org.axis.JMSSOAPListener 4.2. Listener de la distribution 15 Draft Côté serveur ( AXIS ) Draft Le listener JMS précédent est assez simpliste et un listener plus évolué est disponible avec Axis. Il va de pair avec une couche de transport dédiée à JMS côté client est composé d'une classe de transport et d'une classe d'envoi d'un message SOAP via JMS. Figure 4.1. Schéma d'architecture côté serveur Ces classes sont les suivantes: • Listener JMS couplé au moteur org.apache.axis.transport.jms.SimpleJMSListener. SOAP d'Axis: Ce listener prend plusieurs paramètres pour le configurer. Il faut qu'il sache comment récupérer des connexions aux queues jms du serveur et sur laquelle se mettre en écoute. Les premiers paramètres se trouvent dans le fichier de configuration ( connectionfactory.properties ) suivant: java.naming.factory.initial fr.dyade.aaa.jndi2.client.NamingContextFactory java.naming.factory.host localhost java.naming.factory.port 16400 transport.jms.ConnectionFactoryJNDIName=qcf Il faut ensuite coder une classe permettant de passer les paramètres au listener et de le démarrer. La classe suivante se charge de cela: public static void main(String args[]) throws Exception { Options opts = new Options( args ); 16 Draft Côté serveur ( AXIS ) Draft HashMap connectorMap = SimpleJMSListener.createConnectorMap(opts); HashMap cfMap = SimpleJMSListener.createCFMap(opts); String destination = opts.isValueSet('d'); String username = opts.getUser(); String password = opts.getPassword(); // create the jms listener SimpleJMSListener listener=new SimpleJMSListener(connectorMap,cfMap, destination,username, password,false); listener.start(); } ConnectorMap. Cette liste de propriétes permet de définir si on utilise une rubrique JMS ou une queue JMS. Dans le cas d'une queue, elle vide. ConnectionFactoryMap. Cette liste est chargée avec les informations contenues dans le fichier cité précédemment, connectionfactory.properties. Le listener est initialisé avec les paramètres précédents. Le listener est démarré. Sans cela, le listener ne récupèrerait pas les messages et ne les traiterait donc pas. Pour lancer ce listener, il suffit d'utiliser le script axislserver.bat : set JAVA_HOME=d:\jdk1.3.1_01 set set set set set set set set set set set set set set set set set set JORAM_HOME=c:\ws_joram CLASSP=%JORAM_HOME\lib\jakarta-regexp-1.2.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\axis.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\soap.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\commons-logging.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\commons-discovery.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jaxrpc.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\saaj.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jaxp.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\xerces.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\JCup.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jms.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\joram.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\jta.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\log4j.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ow_monolog.jar CLASSP=%CLASSP%;%JORAM_HOME%\lib\ws_joram.jar CONFIG=%JORAM_HOME%\conf\properties %JAVA_HOME%\bin\java -Daxis.ServerConfigFile=%JORAM_HOME%\conf\wsdd\server-config.wsdd -classpath %CLASSP%;%CONFIG% wsjoram.axis.JMSSOAPListenerDistri %1 %2 %3 %4 %5 %6 %7 %8 %9 La commande suivante permet de lancer le listener: > axislserver -c ..\conf\properties\connectionfactory.properties -d JMS_RequestQueue Attention 17 Draft Côté serveur ( AXIS ) Draft Il est à noter que le listener jms de la distribution d'Axis ne supporte que les messages JMS de type BytesMessage. La partie cliente doit donc obligatoirement gérer cela, tant en emission qu'en réception sous peine d'avoir des ClassCastException lors de l'exécution. 18 Draft Draft 5. Côté client Pour cette partie, nous allons utiliser dans un premier temps le listener simple puis celui de la distribution. 5.1. Axis Pour pouvoir invoquer un service web via JMS avec Axis, il faut définir une couche de transport basée sur un handler Axis chargé d'envoyer le message SOAP dans une queue dédiée et créant une queue temporaire pour la réponse. Celle-ci est positionnée sur la queue avec la méthode setReplyTo. Figure 5.1. Schéma d'architecture côté client avec Axis Pour cela, il est nécessaire de configurer la partie cliente avec le fichier de configuration ( clientconfig.wsdd ) suivant: <?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <handler name="JMSMQSender" 19 Draft Côté client Draft type="java:org.axis.JMSSOAPSender"/> <transport name="JMSJoramTransport" pivot="JMSMQSender"/> </deployment> Définition du handler Axis qui se chargera d'envoyer la requête SOAP. Association entre le transport ( JMSJoraTransport ) et le handler précédant. Ceci se fait au moyen du constructeur de la classe Service qui permet de récupérer un objet de la classe Call pour effectuer un appel au service web. Il suffit de spécifier le chemin vers ce fichier dans le constructeur de cette classe. Il suffit ensuite de préciser que l'on va utiliser la couche de transport JMSJoramTransport pour que le handler configuré dans le fichier précédent soit utilisé. Pour le reste, l'invocation du service se fait de la même manière qu'en SOAP sur HTTP. public static void main(String args[]) { try { // Method Name to invoke for the Price Web Service String methodName = "getPrice"; // Objet d'appel du service EngineConfiguration engineConfig=new FileProvider( "../conf/wsdd/client-config.wsdd"); Service service = new Service(engineConfig); Call call = (Call) service.createCall(); QName qname= new QName("PriceService", methodName); call.setOperationName(qname); call.addParameter("price", XMLType.XSD_STRING,ParameterMode.IN); call.setTransport(new JMSJoramTransport()); call.setReturnType(XMLType.XSD_FLOAT); Object[] params = new Object[] { "dollarprice" }; // Appeler le service Float price = (Float) call.invoke(params); System.out.println("Retour : " + price.floatValue()); } catch (Exception e) { ex.printStackTrace(); } } Positionnement du fichier de configuration à utiliser pour l'appel. Positionnement du transport pour l'appel. Le fait de positionner la classe JMSJoramTransport sur l'instance de Call permet grâce au fichier de configuration, d'appeler l'handler pour envoyer la requête SOAP. Celui-ci récupére le message SOAP, le transforme en chaîne de caractères, crée une queue temporaire pour la réponse, positionne celle-ci avec setReplyTo et envoie la requête au moyen de la queue JMS d'envoi. L'handler qui doit hériter de BasicHandler, comporte ici trois parties: • L'initialisation : elle récupére les objets nécessaires pour se connecter à la queue d'émission. 20 Draft Côté client Draft public JMSSOAPSender() { try { System.out.println("JMSSOAP Sender called"); // Get the initial context to lookup the AdministeredObjects InitialContext ictx=getInitialContext(); // Initialize Queue related objects. queueConnFactory= (QueueConnectionFactory)ictx.lookup(JMS_FACTORY); queueConn=queueConnFactory.createQueueConnection(); session=queueConn.createQueueSession( false,Session.AUTO_ACKNOWLEDGE); //Lookup Request Queue queue=(Queue)ictx.lookup(REQUEST_QUEUE); //Create sender object sender=session.createSender(queue); // Create an object of BytesMessage jmsMsg=session.createBytesMessage(); queueConn.start(); } catch (Exception e) { ex.printStackTrace(); } } • L'interception de la requête SOAP : pour un handler, l'interception se fait grâce à l'appel de la méthode invoke. Dans ce code, on retrouve du code similaire à celui du listener JMS précédant. Seuls les nouveaux bouts de code seront expliqués; pour les explications sur les autres parties, se reporter aux paragraphes précédants. public void invoke(MessageContext msgContext) throws AxisFault { try { // Get the request Message from MessageContext org.apache.axis.Message axisMsg=msgContext.getRequestMessage(); // Get a string representation of SOAP Envelope SOAPEnvelope soapEnvelope=axisMsg.getSOAPEnvelope(); Element envElement=soapEnvelope.getAsDOM(); String strSOAPBody=XMLUtils.ElementToString(envElement); System.out.println("JMS-SOAP Request:\n"); System.out.println(strSOAPBody + "\n"); // Set SOAP Envelope as the text message jmsMsg.writeBytes(strSOAPBody.getBytes()); Queue tmpQueueResponse=session.createTemporaryQueue(); jmsMsg.setJMSReplyTo(tmpQueueResponse); System.out.println("Sending JMS-SOAP Request to Request Queue"); sender.send(jmsMsg); System.out.println("Sent JMS-SOAP Request to Response Queue \n"); System.out.println("Waiting to Receive JMS-SOAP Response from Response Queue"); receiver=session.createReceiver(tmpQueueResponse); BytesMessage replyMsg=(BytesMessage)receiver.receive(); System.out.println("Received JMS-SOAP Response from Response Queue\n"); 21 Draft Côté client Draft System.out.println("JMS-SOAP Response:"); System.out.println("\n"); System.out.println("" + replyMsg.getText()); System.out.println("\n"); org.apache.axis.Message responseMsg=getMessage(replyMsg); msgContext.setResponseMessage(responseMsg); System.out.println("End of JMSSOAPSender\n"); receiver.close(); ((TemporaryQueue)tmpQueueResponse).delete(); } catch (Exception e) { throw AxisFault.makeFault(e); } } Création d'une queue JMS temporaire pour la réponse. Réception du message de réponse sur la queue temporaire. Extraction du message SOAP sous forme de chaîne de caractère. Suppression de la queue temporaire. • La libération des ressources : cela comprend la fermeture des connections avec le serveur JMS. public void cleanUp() { try { sender.close(); session.close(); queueConn.close(); } catch (Exception e) { ex.printStackTrace(); } } 5.2. Axis ( classes de la distribution ) Des classes similaires à celles détaillées précédement, sont disponibles dans la distribution d'Axis et peuvent être utilisées telles qu'elles pour remplacer la couche par défaut ( HTTP ) de transport de SOAP par JMS côté client. Ces classes sont les suivantes: • Transport JMS: org.apache.axis.transport.jms.JMSTransport. • Envoi via JMS: org.apache.axis.transport.jms.JMSSender. • Constantes JMS: org.apache.axis.transport.jms.JMSConstants. 5.2.1. Via le transport JMS Le fichier de configuration d'Axis ( client-config-distrib.wsdd ) côté client est le suivant: <deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <handler name="JMSSender" type="java:org.apache.axis.transport.jms.JMSSender"/> <transport name="JMSTransport" pivot="JMSSender"/> </deployment> 22 Draft Côté client Draft Il doit être utilisé de la même manière que dans le précédent paragraphe. Le code d'invocation du service web est similaire à celui du chapitre précédant à deux choses près: • Initialisation du transport via le fichier connectionfactory.properties vu précédemment, • Positionnement explicite que la queue jms d'envoi des messages. EngineConfiguration engineConfig = new FileProvider( "../conf/wsdd/client-config-distrib.wsdd"); Service service = new Service(engineConfig); // create the transport Properties cfProps=new Properties(); cfProps.load(new BufferedInputStream(new FileInputStream( "../conf/properties/connectionfactory.properties"))); JMSTransport transport = new JMSTransport(new HashMap(), new HashMap(cfProps)); // create a new Call object Call call = (Call) service.createCall(); call.setOperationName( new QName("PriceService", "getPrice") ); call.addParameter( "symbol", XMLType.XSD_STRING, ParameterMode.IN ); call.setReturnType( XMLType.XSD_FLOAT ); call.setTransport(transport); // set additional params on the call if desired call.setProperty(JMSConstants.DESTINATION, "JMS_RequestQueue"); call.setTimeout(new Integer(10000)); Object[] params = new Object[] { "dollarprice" }; // Invoke the Price Web Service Float price = (Float) call.invoke(params); // Print out the result System.out.println("The Price is $" + price.floatValue()); Chargement des propriétés relatives à la connexion au serveur JMS. Initialisation du transport. Positionnement du transport pour l'appel. Positionnement de la queue JMS d'envoi. 5.2.2. Via l'url JMS Axis permet également de définir tous ces paramètres dans une url normalisée correspondant à l'url d'invocation du service web ( endpoint ). Dans ce cas, il n'est plus nécessaire de définir le transport que l'on va utiliser. Lorsque le framework SOAP détectera qu'il s'agit d'une url jms, il utilisera le transport jms configuré côté client en utilisant le même fichier que celui du paragraphe précédent. Attention Cette fonctionnalité n'est pas disponible avec la dernière version stable d'Axis ( la version 1.1 ). Il faut récupérer la version de développement dans le cvs et la compiler. Les classes suivantes doivent être présentes: 23 Draft Côté client • org.apache.axis.transport.jms.Handler. • org.apache.axis.transport.JMSURLConnection. Draft Voici un exemple d'url jms: StringBuffer joramJmsUrl=new StringBuffer(); joramJmsUrl.append("jms:/JMS_RequestQueue?"); joramJmsUrl.append("vendor=JNDI"); joramJmsUrl.append("&java.naming.factory.initial= fr.dyade.aaa.jndi2.client.NamingContextFactory"); joramJmsUrl.append("&java.naming.provider.url=localhost:16400"); joramJmsUrl.append("&ConnectionFactoryJNDIName=qcf"); joramJmsUrl.append("&deliveryMode=persistent"); joramJmsUrl.append("&priority=5"); joramJmsUrl.append("&ttl=10000"); joramJmsUrl.append("&debug=true"); Queue d'envoi du message. Type de communication avec JMS. Pour Joram, il faut utiliser JNDI. Classe pour la propriété JNDI java.naming.factory.initial. Localisation du service JNDI du serveur JMS. Nom JNDI de la factory de création de connexions. La façon d'invoquer un service web à partir d'une url jms ressemble fortement aux différentes portions de code étudiées précédemment: EngineConfiguration engineConfig = new FileProvider( "../conf/wsdd/client-config-distrib.wsdd"); Service service=new Service(engineConfig); // create a new Call object Call call=(Call)service.createCall(); call.setOperationName( new QName("PriceService", "getPrice") ); call.addParameter( "symbol", XMLType.XSD_STRING, ParameterMode.IN ); call.setReturnType( XMLType.XSD_FLOAT ); StringBuffer joramJmsUrl=new StringBuffer(); joramJmsUrl.append("jms:/JMS_RequestQueue?"); joramJmsUrl.append("vendor=JNDI"); joramJmsUrl.append("&java.naming.factory.initial= fr.dyade.aaa.jndi2.client.NamingContextFactory"); joramJmsUrl.append("&java.naming.provider.url=localhost:16400"); joramJmsUrl.append("&ConnectionFactoryJNDIName=qcf"); joramJmsUrl.append("&deliveryMode=persistent"); joramJmsUrl.append("&priority=5"); joramJmsUrl.append("&ttl=10000"); joramJmsUrl.append("&debug=true"); java.net.URL jmsurl = new java.net.URL( joramJmsUrl.toString()); call.setTargetEndpointAddress(jmsurl); // set additional params on the call if desired call.setTimeout(new Integer(10000)); Object[] params=new Object[] { 24 Draft Côté client Draft "dollarprice" }; // Invoke the Price Web Service Float price=(Float) call.invoke(params); // Print out the result System.out.println("The Price is $" + price.floatValue()); System.exit(1); Positionnement du fichier de configuration côté client. Création de l'url JMS. Il faut que la distribution ait un handler spécifique pour gérer le protocole "jms" défini. Cette classe doit hériter de java.net.URLStreamHandler. Il s'agit de la classe Handler dans le package org.apache.axis.transport.jms. Positionnement de l'url d'accès au service avec JMS comme couche de transport. 5.3. WSIF WSIF permet d'appeler des services web en se basant sur la description WSDL du service appelée. Les APIs de WSIF sont calquées sur les différentes parties de WSDL : la description générale du service ( types, messages, portType ) et la façon dont il sera invoqué ( binding, opération ). La façon dont on y accède, peut donc changer sans que le code Java soit modifié: WSIF se charge de changer automatiquement de providers en fonction de cela. Figure 5.2. Schéma d'architecture côté client avec WSIF 25 Draft Côté client Draft WSIF se base sur les extensions de WSDL pour définir des binding personnalisés. Dans le cadre de notre étude, cela permet d'utiliser JMS avec un binding SOAP sur JMS et JMS natif. Les attributs permettent de définir les propriétés jndi d'accès au serveur JMS, le nom de la ConnectionFactory ainsi que la queue JMS d'envoi des messages. La description WSDL du service est la suivante: <?xml version="1.0" encoding="UTF-8" ?> <definitions name="Price" targetNamespace="http://xml.apache.org/axis/wsif/ samples/jms/ServiceAvailability" xmlns:tns="http://xml.apache.org/axis/wsif/samples/jms/ServiceAvailability" xmlns:format="http://schemas.xmlsoap.org/wsdl/formatbinding/" xmlns:jms="http://schemas.xmlsoap.org/wsdl/jms/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/"> <message name="getPriceRequest"> <part name="price" type="xsd:string" /> </message> <message name="getPriceResponse"> 26 Draft Côté client Draft <part name="getPriceReturn" type="xsd:float" /> </message> <portType name="GetPricePortType"> <operation name="getPrice"> <input message="tns:getPriceRequest" /> <output message="tns:getPriceResponse" /> </operation> </portType> <binding name="CheckAvailabilityJMSBinding" type="tns:GetPricePortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/jms"/> <operation name="getPrice"> <soap:operation soapAction=""/> <input> <soap:body use="encoded" namespace="http://xml.apache.org/axis/wsif/ samples/jms/ServiceAvailability" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" style="rpc"/> </input> <output> <soap:body use="encoded" namespace="http://xml.apache.org/axis/wsif/ samples/jms/ServiceAvailability" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" style="rpc"/> </output> </operation> </binding> <service name="PriceService"> <port name="GetPricePortType" binding="tns:CheckAvailabilityJMSBinding"> <jms:address destinationStyle="queue" jndiDestinationName="JMS_RequestQueue" jndiConnectionFactoryName="qcf" initialContextFactory="fr.dyade.aaa.jndi2.client.NamingContextFactory" jndiProviderURL="localhost:16400"> <jms:propertyValue name="SOAPAction" type="xsd:string" value=" "/> </jms:address> </port> </service> </definitions> Description des différents messages XML échangés. Description de l'interface du service ( portType ) avec les opérations que celle-ci expose. Description du binding de l'interface. Description du type de binding utilisé au moyen du namespace précisé dans l'attribut type. Cette partie permet de définir l'encodage des messages SOAP. Ici, il s'agit de RPC / encoding. Ceci n'est pas spécifique à JMS mais à SOAP. Description de l'accès à l'interface. Description des différentes propriétés d'accès à l'interface via SOAP sur JMS. Note Pour information, la partie concrète du service ( binding / service ) en JMS natif serait la suivante: 27 Draft Côté client Draft <binding name="CheckAvailabilityJMSBinding" type="tns:GetPricePortType"> <jms:binding type="TextMessage" /> <format:typeMapping encoding="XML" style="Java"> <format:typeMap typeName="xsd:string" formatType="java.lang.String" /> </format:typeMapping> <operation name="getPrice"> <input> <jms:input parts="number" /> <jms:property message="Request" part="myInt" /> <jms:propertyValue name="myLiteralString" type="xsd:string" value="Hello World" /> </input> <output> <jms:output parts="result" /> </output> </operation> </binding> <service name="CheckServiceAvailability"> <port name="GetPricePortType" binding="tns:CheckAvailabilityJMSBinding"> <jms:address destinationStyle="queue" jndiDestinationName="JMS_RequestQueue" jndiConnectionFactoryName="qcf" initialContextFactory= "fr.dyade.aaa.jndi2.client.NamingContextFactory" jndiProviderURL="localhost:16400"/> </port> </service> Comme cela a été dit précédemment, les APIs WSIF se basent directement sur la structure d'un fichier WSDL. Il est possible de faire des appels synchrones et asynchrones ( la réponse est récupérée dans un thread différent de celui de l'appel ). Note Dans ce paragraphe, nous nous baserons sur le listener simple des paragraphes précédants. En fait, pour utiliser, le listener JMS de la distribution Axis, il est nécessaire de patcher le code d'Axis ainsi que celui de WSIF. Nous détaillerons tout cela dans un paragraphe par la suite. 5.3.1. Appel synchrone WSIF peut être utilisé de deux manières. La première, synchrone, consiste à bloquer l'exécution des traitements jusqu'à l'arrivée de la réponse à la demande. Comme le montre le code suivant et comme nous l'avons dit précédemment, le code WSIF se calque sur les éléments WSDL: • Tout d'abord, on charge la définition des services via le fichier WSDL. • Ensuite on récupérer une instance du service correspondant au portType choisi. • On récupère le portType lui-même. • Et enfin l'opération elle-même que l'on peut invoquer avec des paramètres que l'on positionne sur les parties du 28 Draft Côté client Draft message XML à envoyer au service. Il suffit ensuite de l'invoquer, de récupérer la réponse et plus particulièrement les parties du message XML qui nous intéresse. public static void main(String[] args) { try { WSIFPluggableProviders.overrideDefaultProvider( "http://schemas.xmlsoap.org/wsdl/soap/", new WSIFDynamicProvider_ApacheAxis()); Definition def=WSIFUtils.readWSDL(null, "../conf/wsdl/GetPrice.wsdl"); WSIFService sq=WSIFServiceFactory.newInstance().getService(def, null, // serviceNS null, // serviceName "http://xml.apache.org/axis/wsif/samples/jms/ServiceAvailability", // portTypeNS "GetPricePortType"); // portTypeName WSIFPort defPort = sq.getPort(); WSIFOperation getQ = defPort.createOperation("getPrice"); WSIFMessage inMessage = getQ.createInputMessage(); inMessage.setObjectPart("price", "dollarprice"); WSIFMessage outMessage = getQ.createOutputMessage(); WSIFMessage faultMessage = getQ.createFaultMessage(); getQ.executeRequestResponseOperation(inMessage, outMessage,faultMessage); System.out.println("--------------------------------"); for(Iterator i=outMessage.getPartNames();i.hasNext();) { Object o=i.next(); System.out.println("name = "+o); } System.out.println("--------------------------------"); for(Iterator i=outMessage.getParts();i.hasNext();) { Object o=i.next(); System.out.println("o = "+o); } System.out.println("--------------------------------"); System.out.println("getPriceReturn = " +outMessage.getFloatPart("getPriceReturn")); System.out.println("--------------------------------"); } catch (Exception ex) { ex.printStackTrace(); } } Positionnement d'Axis comme moteur SOAP par défaut. Chargement du WSDL du service. Récupération du service. Récupération du portType. Création d'un élément pour appeler l'opération. Création des différents messages et erreurs éventuelles. Envoi de manière synchrone de la requête et attente de la réponse. 29 Draft Côté client Draft Récupération de la partie du message de retour nous intéressant. 5.3.2. Appel asynchrone La seconde manière d'utiliser WSIF, asynchrone, consiste à recevoir la réponse dans un thread différent de celui de la demande. Cela permet de ne pas bloquer l'exécution des traitements jusqu'à l'arrivée de celle-ci. Pour ce faire, il faut définir une classe handler implémentant WSIFResponseHandler et qui sera appelée, via la méthode, executeAsyncResponse, lors de la réception de la réponse. Les paramètres de cette méthode seront logiquement le message de retour et les fautes éventuelles levées. import org.apache.wsif.WSIFResponseHandler; import org.apache.wsif.WSIFMessage; import java.util.Iterator; public class JMSSOAPClientReponse implements WSIFResponseHandler { public JMSSOAPClientReponse() { } public void executeAsyncResponse(WSIFMessage outMessage,WSIFMessage fault) { try { for(Iterator i=outMessage.getPartNames();i.hasNext();) { Object o=i.next(); System.out.println("name = "+o); } System.out.println("--------------------------------"); for(Iterator i=outMessage.getParts();i.hasNext();) { Object o=i.next(); System.out.println("o = "+o); } System.out.println("--------------------------------"); System.out.println("getPriceReturn = " +outMessage.getFloatPart("getPriceReturn")); System.out.println("--------------------------------"); } catch(Exception ex) { ex.printStackTrace(); } } } La première partie du code ( juste qu'à l'invocation du service ) est la même que pour un appel synchrone. Ce qui diffère, est le fait la réponse est trappée par l'handler. public static void main(String[] args) { try { WSIFPluggableProviders.overrideDefaultProvider( "http://schemas.xmlsoap.org/wsdl/soap/", new WSIFDynamicProvider_ApacheAxis()); Definition def=WSIFUtils.readWSDL(null, "../conf/wsdl/GetPrice.wsdl"); WSIFService sq=WSIFServiceFactory.newInstance().getService(def, null, // serviceNS null, // serviceName "http://xml.apache.org/axis/wsif/samples/jms/ServiceAvailability", // portTypeNS "GetPricePortType"); // portTypeName WSIFPort defPort = sq.getPort(); 30 Draft Côté client Draft WSIFOperation getQ = defPort.createOperation("getPrice"); WSIFMessage inMessage = getQ.createInputMessage(); inMessage.setObjectPart("price", "dollarprice"); JMSSOAPClientReponse reponseHandler=new JMSSOAPClientReponse(); getQ.executeRequestResponseAsync(inMessage, reponseHandler); } catch (Exception ex) { ex.printStackTrace(); } } Création du handler de récupération de la réponse. Envoi de manière asynchrone de la requête sans attendre de la réponse qui sera traitée par l'handler. Note L'appel asynchrone avec WSIF ne fonctionne pas très bien. Le client n'arrive pas à récupérer la réponse dans un autre thread que celui de l'exécution. Pour que cela fonctionne, il faut également patcher WSIF comme décrire dans le paragraphe suivant. 31 Draft Draft 6. Patcher Axis et WSIF Le but de ce paragraphe est de détailler les modifications à apporter aux codes sources d'Axis et WSIF pour que l'un puisse fonctionner avec l'autre en SOAP sur JMS. En effet plusieurs problèmes se posent avec les versions présentes dans les cvs respectifs en date du 06/11/2003. Ces problèmes sont les suivants: • WSIF utilise les messages JMS TextMessage comme transport JMS du message alors que Axis, de son côté, ne gère que les messages de type BytesMessage. • Une fois le premier problème résolu, Axis renvoie bien le message mais WSIF n'arrive pas à le relier avec le message envoyé car l'identifiant de corrélation n'est pas positionné avec l'identifiant du message reçu. 6.1. Patcher Axis Les modifications à apporter au listener JMS d'Axis sont minimes. Elles se trouvent dans la classe SimpleJMSWorker du package org.apache.axis.transport.jms. Elles portent sur le bloc de code situé entre les lignes 142 et 158. try { // now we need to send the response Destination destination = message.getJMSReplyTo(); if(destination == null) return; JMSEndpoint replyTo = listener.getConnector().createEndpoint(destination); HashMap properties=new HashMap(); properties.put(JMSConstants.JMS_CORRELATION_ID, message.getJMSMessageID()); ByteArrayOutputStream out = new ByteArrayOutputStream(); msg.writeTo(out); replyTo.send(out.toByteArray(),properties); } catch(Exception e) { e.printStackTrace(); } Ajouter la propriété de corrélation dans la liste des propriétés du message de retour. Passage des propriétés au JMSEndpoint pour les affecter au message à renvoyer. 6.2. Patcher WSIF Les modifications à apporter au handler d'Axis utilisé par WSIF pour envoyer des messages via JMS sont également minimes. Elles sont constituées de deux parties: envoi et réception des messages. Elles se situent toutes dans la classe WSIFJMSDestination du package org.apache.wsif.util.jms. 6.2.1. Envoi des messages Il s'agit de placer la requête SOAP dans un message BytesMessage plutôt que dans un message TextMessage pour que celui-ci puisse être traité par le listener JMS d'Axis. Ces modifications se situent dans la méthode suivante: • public String send(String data, String id) throws WSIFException 32 Draft Patcher Axis et WSIF Draft /** * Send a message to the write queue * @param data is the message * @param id is the correlation id to set on the message * @return the id of the message that was sent. */ public String send(String data, String id) throws WSIFException { Trc.entry(this, data, id); areWeClosed(); try { /*TextMessage msg = session.createTextMessage(); msg.setText(data);*/ BytesMessage msg = session.createBytesMessage(); msg.writeBytes(data.getBytes()); String s = send(msg, id, true); Trc.exit(s); return s; } catch (JMSException je) { Trc.exception(je); throw WSIFJMSConstants.ToWsifException(je); } } Code initial. Code ajouté. 6.2.2. Réception des messages ( synchrone ) Il s'agit de faire gérer les messages BytesMessage en réception par WSIF. Pour ce faire, une méthode a été ajoutée pour extraire les informations SOAP contenues dans ceux-ci sous forme de chaine de caractères. • private String getMessageString(BytesMessage message) private String getMessageString(BytesMessage message) { try { // get the incoming msg content into a byte array byte[] buffer = new byte[8 * 1024]; ByteArrayOutputStream out = new ByteArrayOutputStream(); for(int bytesRead = message.readBytes(buffer); bytesRead != -1; bytesRead = message.readBytes(buffer)) { out.write(buffer, 0, bytesRead); } return out.toString(); } catch(Exception e) { e.printStackTrace(); return null; } } Les modifications pour gérer les BytesMessage se trouvent dans la méthode suivante: • public String receiveString(String id, long timeout) throws WSIFException /** * Blocking receive waits for the specified timeout 33 Draft Patcher Axis et WSIF Draft * @return the received message */ public String receiveString(String id, long timeout) throws WSIFException { Trc.entry(this, id); Message msg = receive(id, timeout); String s = null; try { if (msg instanceof TextMessage) s = ((TextMessage) msg).getText(); else if (msg instanceof BytesMessage) s = getMessageString((BytesMessage) msg); else throw new WSIFException( "Reply message was not a TextMessage:msg=" + (msg == null ? "null" : msg.toString())); } catch (JMSException e) { Trc.exception(e); throw WSIFJMSConstants.ToWsifException(e); } Trc.exit(s); return s; } Code ajouté pour gérer les BytesMessage en retour. 6.2.3. Réception des messages ( asynchrone ) Le support des échanges asynchrones avec un binding SOAP sur JMS est prévu dans WSIF, mais ne fonctionne pas. En fait, il manque le thread de récupération de la réponse. Celui-ci a pour but de récupérer celle-ci et de notifier l'opération pour qu'elle fasse suivre celle-ci au handler. Le thread est vraiment très simple. Nous l'avons nommé WSIFJmsReceiverThread et placé dans le package org.apache.wsif.providers.soap.apacheaxis. package org.apache.wsif.providers.soap.apacheaxis; import org.apache.wsif.WSIFOperation; import org.apache.wsif.util.jms.WSIFJMSDestination; import javax.jms.Message; public class WSIFJmsReceiverThread implements Runnable { private Thread runner; private WSIFOperation wsifOp; private WSIFJMSDestination dest; private long syncTimeout; private String msgID; public WSIFJmsReceiverThread(WSIFOperation wsifOp,WSIFJMSDestination dest, String msgID,long syncTimeout) { this.wsifOp=wsifOp; this.dest=dest; this.syncTimeout=syncTimeout; this.msgID=msgID; runner=new Thread(this); runner.start(); } public void run() { try { 34 Draft Patcher Axis et WSIF Draft Message msg=dest.receive(msgID,syncTimeout); wsifOp.fireAsyncResponse(msg); } catch(Exception ex) { ex.printStackTrace(); } } } Initialisation du thread avec les informations de la demande nécessaires. Mise en attente sur la queue JMS de réponse. Passage de la réponse à l'opération lorsque celle-ci réponse arrive. Il faut ensuite lancer ce thread lors de l'appel d'une méthode d'un service web de manière asynchrone. L'envoi du message se fait via un handler Axis ( lors que l'on choisit Axis comme provider pour SOAP ). Il s'agit de la classe suivante: • org.apache.wsif.providers.soap.apacheaxis.WSIFJmsSender. Dans celle-ci, l'appel asynchrone se fait au moyen de la méthode suivante: • private void performAsyncSend( MessageContext WSIFJMSDestination dest, String data) throws WSIFException messageContext, Le code à ajouter dans cette méthode est simplement l'instanciation du thread de réception du message JMS. private void performAsyncSend( MessageContext messageContext, WSIFJMSDestination dest, String data) throws WSIFException { String msgID; WSIFOperation_ApacheAxis wsifOp = (WSIFOperation_ApacheAxis) messageContext.getProperty( WSIFJmsTransport.WSIFOPERATION); WSIFCorrelationId cid; // only save op in the correlation service if there's a response handler if (wsifOp.getResponseHandler() == null) { msgID = dest.send(data); cid = new WSIFJMSCorrelationId(msgID); } else { Long transportAsyncTimeoutValue = (Long) messageContext.getProperty( WSIFJmsTransport.ASYNC_TIMEOUT); long asyncTimeout = transportAsyncTimeoutValue == null ? ASYNC_TIMEOUT : transportAsyncTimeoutValue.longValue(); WSIFCorrelationService correlator = WSIFCorrelationServiceLocator.getCorrelationService(); synchronized (correlator) { msgID = dest.send(data); cid = new WSIFJMSCorrelationId(msgID); if (correlator != null) { correlator.put(cid, (Serializable) wsifOp, asyncTimeout); } WSIFJmsReceiverThread thread = 35 Draft Patcher Axis et WSIF Draft new WSIFJmsReceiverThread(wsifOp,dest,msgID,asyncTimeout); } } // Save msg ID in the WSIFop for this calling client wsifOp.setAsyncRequestID(new WSIFJMSCorrelationId(msgID)); // Axis doesn't like a null response so give it something Message responseMessage = new Message(DUMMY_RESPONSE); messageContext.setResponseMessage(responseMessage); } Envoi du message SOAP car on se trouve dans le cas où l'handler est défini ( donc non null ). Lancement du thread de réception du message SOAP de retour. De plus, lorsque l'on fait suivre le message à l'opération, elle ne peut pas le traiter car elle ne sait gérer que les messages de type TextMessage et pas ceux de type BytesMessage. Pour ce faire, il faut modifier le code de la classe suivante: • org.apache.wsif.providers.soap.apacheaxis.WSIFOperation_ApacheAxis. Il faut tout d'abord ajouter la méthode suivante qui a déjà été décrite précédemment: • private String getMessageString(BytesMessage message) Puis permettre à le méthode suivante de traiter les messages BytesMessage comme décrite ici. • private Object deserialiseResponseObject(Object msg) throws WSIFException private Object deserialiseResponseObject(Object msg) throws WSIFException { if (msg == null) { throw new WSIFException("null response to async send"); } /*if (!(msg instanceof TextMessage)) { throw new WSIFException("response not a javax.jms.TextMessage"); }*/ if (!(msg instanceof BytesMessage)) { throw new WSIFException("response not a javax.jms.BytesMessage"); } try { /*TextMessage m = (TextMessage) msg; Message responseMessage = new Message(m.getText()); responseMessage.setMessageType(Message.RESPONSE);*/ BytesMessage m = (BytesMessage) msg; Message responseMessage = new Message(getMessageString(m)); responseMessage.setMessageType(Message.RESPONSE); Service service = new Service(); MessageContext msgContext = new MessageContext(service.getEngine()); msgContext.setResponseMessage(responseMessage); TypeMappingRegistry tmr = msgContext.getTypeMappingRegistry(); org.apache.axis.encoding.TypeMapping tm = (org.apache.axis.encoding.TypeMapping) tmr.getTypeMapping( outputEncodingStyle); // register any default type mappings 36 Draft Patcher Axis et WSIF registerDefaultTypeMappings(tm, getContext()); // register any mappings from WSIFService.mapType calls registerDynamicTypes(tm, typeMap, getContext()); Message resMsg = msgContext.getResponseMessage(); SOAPEnvelope resEnv = resMsg.getSOAPEnvelope(); Object b = resEnv.getFirstBody(); if (b instanceof SOAPFaultElement) { return new AxisFault(b.toString()); } RPCElement body = (RPCElement) b; Object result = null; HashMap outParams; Vector resArgs = body.getParams(); if (resArgs != null && resArgs.size() > 0) { RPCParam param = (RPCParam) resArgs.get(0); result = param.getValue(); if (resArgs.size() > 1) { outParams = new HashMap(); for (int i = 1; i < resArgs.size(); i++) { RPCParam p = (RPCParam) resArgs.get(i); outParams.put(p.getName(), p.getValue()); } setResponseMessageParameters(outParams); } } return result; } catch (Exception ex) { Trc.exception(ex); throw new WSIFException(ex.getMessage()); } } Ancienne vérification du type du message ( TextMessage ). Nouvelle vérification du type du message ( BytesMessage ). Ancienne récupération du texte contenu dans le message ( TextMessage ). Nouvelle récupération du texte contenu dans le message ( BytesMessage ). 37 Draft Draft Draft Bibliographie Documentations joram.objectweb.org/ [JoramInstall36] Installing JORAM 3.6. Ce document est disponible à http://joram.objectweb.org/current/doc/joram3_6_INSTALL.pdf. Frédéric Maistre. [JoramAdmin36] Administering JORAM 3.6. Ce document est disponible à http://joram.objectweb.org/current/doc/joram3_6_ADMIN.pdf. Frédéricric Maistre. [JoramSamples36] JORAM 3.6 Samples. Ce document est disponible à http://joram.objectweb.org/current/doc/joram3_6_SAMPLES.pdf. Frédéric Maistre. l'url l'url l'url suivante : suivante : suivante : Documentations www.webservices.org/ [Chappel03] Asynchronous Web Services and the Entreprise Bus. Ce document est disponible à l'url suivante : http://www.webservices.org/index.php/article/articleview/352/. David A. Chappell. Documentations webservices.devchannel.org/ [Eekim01] Integrating Diverse Software Systems using WSDL and WSIF. Ce document est disponible à l'url suivante : http://webservices.devchannel.org/article.pl?sid=03/04/22/068231&mode=thread. Eekim. Documentations www-106.ibm.com/developerworks/webservices/ [Naveen03] Developing synchronous JMS SOAP applications. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/webservices/library/ws-jms/. Navenn Belami. [Nara03] Approches to asynchronous Web services. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/webservices/library/ws-asoper/. Narayanan Mouli et T.V. Srivathsa. [Adams01] Asynchronous operations and the Web services, Part 1 : A primer asynchronous transactions. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/webservices/library/ws-asynch1.html. Holt Adams. [Adams02] Asynchronous operations and the Web services, Part 2. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/webservices/library/ws-asynch2/. Holt Adams. [Adams03] Asynchronous operations and the Web services, Part 3 : Add business semantics to Web services. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/webservices/library/ws-asynch3/. Holt Adams. [Siddi01] Deploying Web services in WSDL, part 1. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/library/ws-intwsdl/. Bilal Siddiqui. [Siddi02] Deploying Web services in WSDL, part 2 : Simple Object Access Protocol ( SOAP ). Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/library/ws-intwsdl2/. Bilal Siddiqui. [Siddi03] Deploying Web services in WSDL, part 3 : SOAP interoperability. Ce document est disponible à l'url suivante : http://www-106.ibm.com/developerworks/library/ws-intwsdl3/. Bilal Siddiqui. 38 Draft Bibliographie Draft Documentations ws.apache.org/axis/ [ChappellAxis03] Overview of the JMS Transport Layer. Ce document est disponible à l'url suivante : http://cvs.apache.org/viewcvs.cgi/*checkout*/ws-axis/java/samples/jms/AxisJMSSample.pdf?rev=1.1.4.1& content-type=application/pdf. Dave Chappell. [ChappellAxis03] JMS URL Syntax for the Apache Axis Project. Dave Chappell, Ray Chun, et Jaime Meritt. Documentations ws.apache.org/wsif/ [Wsif01] WSDL JMS Extension. Ce document est disponible http://ws.apache.org/wsif/providers/wsdl_extensions/jms_extension.html. à l'url [Wsif02] JMS Sample. Ce document est disponible à l'url http://cvs.apache.org/viewcvs/~checkout~/ws-wsif/java/samples/jms/README.html. 39 suivante suivante : :