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
:
:

Documents pareils