JMS Chap #5 - chatel.pm
Transcript of JMS Chap #5 - chatel.pm
Introduction
Le but de ce chapitre est notamment :
âą De rappeler les concepts clĂ©s de la communication par message au travers de lâAPI normalisĂ©e JMS (Java Messaging Service).
âą De montrer les problĂšmes liĂ©s Ă la diffusion dâinformation non seulement en point Ă point mais aussi Ă un groupe de destinataires.
Ce chapitre abordera également les notions de programmation asynchrone et de qualité de service.
JMS Chap #5
2
Java Messaging ServiceUne API standard
JMS est une API standard de Java EE (Enterprise Edition). Elle permet de fournir à java une implémentation de MOM (Message Oriented Middleware) ou intergiciel de communication par messages, en quelque-sorte un service de mail pour applications.
Toutefois, pour pouvoir utiliser JMS, il faut disposer dâune implĂ©mentation externe en Java. Il existe de nombreuses implĂ©mentations, tant commerciales que Open Source.
Citons par exemple : WebSphere MQ dâIBM, Sonic MQ de Progress Software mais aussi Fiorano et Swift MQ pour les versions commerciales ; ActiveMQ, Joram, OpenJMS pour les versions Open Source.
Il existe Ă©galement des utilitaires pour tester la conformitĂ© dâune implĂ©mentation donnĂ©e Ă lâAPI officielle : http://jmscts.sourceforge.net/
JMS Chap #5
3
Java Messaging ServiceUne API standard
Nb : notez bien que lâAPI JMS est dĂ©finie dans la versions serveur Java EE de Java et non la version standard Java SE.
Il y a eu une remise Ă plat entre JMS 1.0 et JMS 1.1 afin dâunifier les noms des mĂ©thodes et les classes entre les deux modes de communication :
⹠Par exemple publish et send sont unifiés en send.
âą Ce cours sâappuie sur la version 1.1 de JMS.
JMS Chap #5
4
Java Messaging ServiceCommunication Ă plusieurs
Alors que dans les technologies RPC telles que RMI ou CORBA il y a une communication synchrone directe entre un objet client et un objet serveur, JMS introduit une couche de médiation entre un ou plusieurs producteurs et un ou plusieurs consommateurs.
âą Un producteur de message va crĂ©er un message et utiliser une implĂ©mentation JMS pour lâenvoyer.
âą Un consommateur va en contrepartie lire le message (le consommer).
Typiquement, une communication RPC nécessitera de nombreux liens point à point entre clients et serveurs (cf. figure suivante) :
JMS Chap #5
Client A Client C
Client B Client D
5
Java Messaging ServiceCommunication Ă plusieurs
La couche de médiation JMS permet par contre une communication de un ou plusieurs producteurs vers 0 ou plusieurs consommateurs.
Attention Ă ne plus parler de clients et de serveurs pour les consommateurs et les producteurs, car en quelque sorte tous les producteurs et consommateurs sont vus comme des clients par le courtier (âbrokerâ) JMS (cf. figure suivante) :
âą La communication sâĂ©tablira au travers de deux segments : par exemple entre A et B, A Ă©tablira une communication avec le courtier JMS, ensuite B fera de mĂȘme, Ă un instant pouvant ĂȘtre diffĂ©rĂ©.
âą Le courtier peut en effet stocker des messages de façon persistante, afin dâassurer une communication asynchrone : tout comme lorsque vous envoyez du courrier Ă un correspondant, celui-ci pourra le lire Ă une date ultĂ©rieure depuis sa boĂźte aux lettres (BAL).
âą Sa BAL sera identifiĂ©e par une Destination de type Queue ou Topic, selon que lâon est en mode point Ă point ou en diffusion.
JMS Chap #5
6
Interconnexion entre plusieurs interlocuteurs au travers dâun courtier JMS :
Client A Client C
Client B Client D
JMS
Server
Java Messaging ServiceCommunication Ă plusieurs JMS Chap #5
7
Java Messaging ServiceDestinations
Le modĂšle JMS provient de lâunification de 2 modes de communication :
âą Un mode de communication en point Ă point avec un stockage temporaire dans une boĂźte aux lettre appelĂ©e Queue, oĂč un producteur va dĂ©poser un message lu ultĂ©rieurement par un consommateur.
âą Un mode communication par Ă©vĂ©nements, ou en mode publication/abonnement (âpublish/subscribeâ), oĂč 0 ou plusieurs consommateurs Ă©coutent sur un mĂȘme canal de diffusion appelĂ© Topic, alimentĂ© par un ou plusieurs producteurs.
Les destinations de type Queue ou Topic Ă©vitent au producteur et au consommateur de connaĂźtre leurs adresses respectives, et elles assurent la transparence de localisation, contrairement aux sockets oĂč lâon doit connaĂźtre le port et le nom des machines pour communiquer.
JMS Chap #5
8
Java Messaging ServiceDestinations
Les destinations peuvent ĂȘtre ou non administrĂ©s :
âą Certaines implĂ©mentations nĂ©cessitent la crĂ©ation de Queue dans un outil dâadministration et non dynamiquement dans un programme.
âą En effet, les Queue sont souvent liĂ©es Ă une capacitĂ© de stockage physique. Lâoutil dâadministration permet alors de consulter lâĂ©tat de la Queue, le nombre de messages en attente.
âą Les Topics peuvent Ă©galement parfois ĂȘtre administrĂ©s.
Le mode de communication en point Ă point est bien adaptĂ© Ă un workflow dâentreprise oĂč les documents circulent avec un temps de traitement spĂ©cifique (cf. figure suivante).
JMS Chap #5
9
Mode point Ă point de circulation des documents :
Production
Service
Vente
Facturation Caisse
Facture
Bon de
Livraison
Commande
Java Messaging ServiceDestinations JMS Chap #5
10
Java Messaging ServiceDestinations
Le mode de communication par événement convient au contraire pour diffuser des alertes à plusieurs destinataires (cf. figure suivante), par exemple des informations de bourse à des courtiers, ou bien des anomalies réseau en télécommunications, des points de synchronisation en vidéo, etc...
Diffusion dâalertes de cours de change Ă la Bourse :
JMS Chap #5
Courtier
Courtier
Banque
Banque
11
Java Messaging ServiceDestinations
La plupart des implĂ©mentations de JMS permettent la communication sous les 2 modes, toutefois certaines nâimplĂ©mentent parfois que le mode Topic : elles ne sont alors pas conformes Ă la spĂ©cification JMS.
Chaque message individuel nâest produit que par un seul producteur, mĂȘme si globalement une destination donnĂ©e peut ĂȘtre alimentĂ©e par plusieurs producteurs.
En mode point Ă point, un message individuel nâest lu que par un seul destinataire consommateur (cf. figure suivante).
JMS Chap #5
Producteur
1
Producteur
2
Consommateur
1
Consommateur
2
Mess 1
Mess 2
Mess 3
File de messages (Queue)
12
Java Messaging ServiceDestinations
Le message peut ĂȘtre stockĂ© pour un certain temps par le courtier, et la lecture du message peut ĂȘtre diffĂ©rĂ©e par rapport Ă sa production. Câest en ce sens que lâon dira que JMS est un mode de communication asynchrone. AprĂšs lecture, le message est dĂ©truit de son lieu de stockage aprĂšs accusĂ© de rĂ©ception du consommateur.
Dans un mode publication/abonnement, plusieurs destinataires consommateurs Ă©coutent sur le mĂȘme canal (Topic) et reçoivent collectivement le mĂȘme message individuel (cf. figure suivante).
JMS Chap #5
Producteur
1
Producteur
2
Consommateur
1
Consommateur
2
Mess 1
Mess 2
Mess 3
Message Topic
13
Java Messaging ServiceDestinations
Comme les consommateurs peuvent sâabonner et se dĂ©sabonner dynamiquement, il nâest pas possible de connaĂźtre le nombre dâaccusĂ©s de rĂ©ception Ă attendre. Le message est donc dĂ©truit immĂ©diatement, sauf dans le cas particulier des abonnĂ©s durables que nous verrons par la suite.
âą En mode publication/abonnement, un consommateur absent au moment de lâenvoi du message ne le recevra donc pas. Câest pour cela quâil est considĂ©rĂ© moins fiable que le mode point Ă point : câest la mĂȘme analogie quâentre le mode TCP et le mode UDP.
A quel moment exact le message est-il dĂ©truit ? Câest ce que nous allons voir avec les concepts de session et de connexion.
JMS Chap #5
14
Java Messaging ServiceStructure des messages
Contrairement aux modĂšles RPC (CORBA, RMI, SOAP, ...), les consommateurs et le producteurs ne partagent pas dâinterfaces et sâĂ©changent uniquement des informations sous forme de messages.
Un message est par dĂ©finition la composition dâun en-tĂȘte, de propriĂ©tĂ©s et dâun contenu, ce contenu pouvant ĂȘtre de plusieurs natures : texte, binaire ou autre, comme nous le verrons par la suite. Câest pour cette raison que JMS est souvent utilisĂ© pour lâintĂ©gration dâapplications puisquâil est moins contraignant sur les dĂ©finitions des interfaces et sâadapte Ă lâexistant sans modification.
Le message Ă un rĂŽle ambivalent puisquâil sert :
âą De transport dâinformation.
⹠De point de synchronisation entre producteur(s) et consommateur(s) lorsque le consommateur utilise une réception synchrone avec une méthode receive().
JMS Chap #5
15
Java Messaging ServiceConnexion et session
Il existe deux abstractions en JMS qui nâont pas dâĂ©quivalent en mode RPC : la connexion et la session :
âą La connexion est connue des utilisateurs de bases de donnĂ©es et de lâinterface JDBC (Java Database Communication), elle permet notamment de gĂ©rer la sĂ©curitĂ© des accĂšs et le dĂ©roulement des transactions vers le gestionnaire de bases de donnĂ©es.
⹠La session, on le verra, joue un rÎle dans la qualité de service propre à JMS.
JMS Chap #5
16
Java Messaging ServiceConnexion = Fiabilité
La connexion assure la fiabilité :
âą Lorsque lâon utilise des sockets, un mode connectĂ© prĂ©cise que le client et le serveur doivent Ă©changer une poignĂ©e de main (handshake) avant de rentrer en communication. Ce protocole assure que les deux parties sont disponibles au moment de la communication.
âą Si lâun des deux raccroche, la communication est interrompue. Câest ainsi que le protocole TCP qui est en mode connectĂ© est considĂ©rĂ© comme plus fiable quâUDP, oĂč le message peut se perdre si le correspondant a raccrochĂ©.
âą La connexion JMS joue exactement ce rĂŽle : elle assure la fiabilitĂ© entre un client JMS et son courtier, et lâon peut rĂ©cupĂ©rer les exceptions si la connexion est dĂ©faillante.
JMS Chap #5
17
Java Messaging ServiceSession = Qualité de Service
La connexion assure la Qualité de service :
âą Une connexion peut ouvrir plusieurs sessions (cf. figure suivante). Une session est dĂ©finie Ă lâintĂ©rieur dâune connexion comme une dĂ©limitation temporelle permettant de grouper des envois ou des rĂ©ceptions de messages avec des propriĂ©tĂ©s spĂ©cifiques :
- mode transactionnel ou non, priorités des messages, séquence des messages, gestion des accusés de réception.
- Câest ce que lâon appellera la QualitĂ© de Service !
⹠Naturellement, deux sessions consécutives peuvent définir des modes différents.
En conclusion, dans JMS, la sécurité est gérée par la connexion, les transactions par la session.
JMS Chap #5
18
Exemple de codeEnvoi dâun message
Un message est tout simplement un objet de la classe Message. Nous prendrons ici le type de message le plus simple, TextMessage, dont le contenu est une chaĂźne de caractĂšres.
âą On peut accĂ©der au contenu dâun TextMessage en utilisant les mĂ©thodes getText et setText(String).
Nb : Les autres types de message seront détaillés plus loin dans ce chapitre.
Pour envoyer un message, il faut disposer dâune ConnectionFactory, et connaĂźtre dâautre part le nom dâune destination, Queue ou Topic, (ici MyQueue).
âą La ConnectionFactory utilise le design pattern de la fabrique et permet plusieurs connexions concurrentes. Elle encapsule tous les paramĂštres dâinitialisation de la connexion.
JMS Chap #5
19
Exemple de codeEnvoi dâun message
âą La ConnectionFactory peut provenir dâun annuaire JNDI ou ĂȘtre crĂ©e Ă partir de paramĂštres de configuration.
âą La destination peut Ă©galement ĂȘtre stockĂ©e dans un annuaire JNDI. Ceci est en particulier utilisĂ© pour les destinations administrĂ©es et crĂ©Ă©es par lâoutil dâadministration propre Ă lâimplĂ©mentation JMS.
La crĂ©ation dâune ConnectionFactory est spĂ©cifique Ă une implĂ©mentation, ici il sâagit de Sonic MQ. Le paramĂštre nĂ©cessaire est lâadresse du serveur et son numĂ©ro de port :
On nâa besoin que dâun seul package pour JMS en plus des bibliothĂšques liĂ©es Ă lâimplĂ©mentation :
JMS Chap #5
tcp://localhost:2506
import java.jms.*;
20
Exemple de codeEnvoi dâun message
On crée alors la ConnectionFactory :
La connexion nĂ©cessite un nom dâutilisateur et un mot de passe, mails il est possible dâutiliser le login par dĂ©faut :
AprĂšs la crĂ©ation de la connexion, il faut une session. Ici la session ne comporte pas de transaction (paramĂštre false), et lâacquittement des messages est automatique (AUTO_ACKNOWLEDGE).
JMS Chap #5
String broker = âtcp://localhost:2506â;ConnectionFactory qcf = (ConnectionFactory) new progress.message.jclient.ConnectionFactory(broker);
Session session = connexion.createSession(false, Session.AUTO_ACKNOWLEDGE);
Connection connexion = qcf.createConnection(âUtilisateurâ, âMotDePasseâ);
21
Exemple de codeEnvoi dâun message
La Queue peut avoir Ă©tĂ© crĂ©Ă©e dans le courtier avant utilisation, cela dĂ©pend des implĂ©mentations. On peut Ă©galement avoir des destinations temporaires liĂ©es Ă la session. Lâinstruction suivante donne seulement une rĂ©fĂ©rence sur cet espace de stockage crĂ©Ă© Ă lâavance par un outil dâadministration propre Ă lâimplĂ©mentation. Ici la destination a pour nom MyQueue.
On crĂ©e ensuite le producteur et le message. Nb : Le producteur et le message nâexistent que dans la session qui les crĂ©e.
Il ne faut ensuite pas oublier de fermer la connexion. Ceci ferme Ă©galement la session et dĂ©clenche tous les messages de gestion dâaccusĂ©s rĂ©ception :
JMS Chap #5
MessageProducer sender = session.createProducer(queue);TextMessage mess = session.createTextMessage(âHelloâ);sender.send(mess);
Queue queue = session.createQueue(âMyQueueâ);
session.close();connexion.close();
22
Exemple de codeEnvoi dâun message
Le code est exactement le mĂȘme en mode publication/abonnement. La seule diffĂ©rence est le type de destination : ici un Topic.
Les Topic sont le plus souvent crĂ©Ă©s dynamiquement, contrairement aux Queue, car dans le mode usuel il nây a pas dâacquittement des Topic, et donc pas de persistance des messages et de rĂ©servation de stockage.
La symétrie entre Topic et Queue est identique dans le code de réception de la section suivante.
JMS Chap #5
Topic topic = session.createTopic(âMonTopicâ);
23
Exemple de codeRĂ©ception synchrone dâun message
Pour la rĂ©ception dâun message synchrone, la crĂ©ation dâune connexion et dâune session sont identiques, de mĂȘme que la crĂ©ation de la rĂ©fĂ©rence Ă la Destination.
Toutefois, la session va cette fois crĂ©er un consommateur (Consumer) au lieu dâun producteur (Producer).
Nb : On peut crĂ©er dans la mĂȘme session des producteurs et des consommateurs.
JMS Chap #5
Queue queue = session.createQueue(âMyQueueâ);//Pour un Topic, on aurait de mĂȘme createTopic(...)
MessageConsumer receiver = session.createConsumer(queue);//Ne pas oublier de demarrer la connexion pour lâattete de reception de messagesconnexion.start();
//Reception dâun message de type texteTextMessage mess = (TextMessage) receiver.receive();System.out.println(âMessage recu : â + mess.getText());}//Fermeture de la connexion et, par lĂ mĂȘme, de la sessionconnexion.close();
24
Exemple de codeRĂ©ception synchrone dâun message
Ne pas oublier de démarrer la connexion (start()) pour se mettre en attente active de réception des messages.
Ne pas oublier de fermer la connexion, afin de libĂ©rer des ressources. Le programme restera en attente tant que la connexion nâa pas Ă©tĂ© fermĂ©e. Certaines implĂ©mentations nĂ©cessitent de fermer la session avant la connexion.
La commande de rĂ©ception receive est bloquante : si il nây a pas de message, le consommateur sera bloquĂ©â en attente indĂ©finiment. Si lâon souhaite Ă©viter ce blocage, on peut utiliser :
âą ReceiveNoWait() : renvoie le premier message dans la Queue si il existe, sinon renvoie null.
âą Receive(long timeout) : attend un message pendant lâintervalle de temps timeout, sinon renvoi null.
JMS Chap #5
25
Exemple de codeRĂ©ception asynchrone dâun message
Pour une réception de message utilisant receive, le consommateur est bloqué en attente de réception (cf. figure suivante). En ce sens, on parle de réception synchrone.
Le terme est ambivalent, car on qualifiera JMS de mode de communication asynchrone entre producteurs et consommateurs, car le moment dâĂ©mission du message peut ĂȘtre totalement dĂ©couplĂ© du moment de rĂ©ception du message, tout comme pour une lettre traditionnelle.
La confusion provient du fait quâune communication par JMS comporte 2 segments distincts : lâun entre lâĂ©metteur et le courtier, lâautre entre le courtier et le rĂ©cepteur.
âĄGlobalement la communication est asynchrone dans JMS, mais le segment du rĂ©cepteur peut ĂȘtre synchrone ou asynchrone.
JMS Chap #5
26
Exemple de codeRĂ©ception asynchrone dâun message
Le consommateur est bloquĂ© en attente sur un receive tant quâil nây a pas de message :
JMS Chap #5
Courtier Consommateur
receive
[message disponible]
Blocage
27
Exemple de codeRĂ©ception asynchrone dâun message
Lâattente bloquante peut dĂ©tĂ©riorer les performances, ou gĂ©nĂ©rer des interblocages :
âą Un interblocage survient lorsque 2 interlocuteurs sont chacun en attente dâun message de lâautre.
⹠Par ex. si A attend un message de B pour lui envoyer une réponse, et B attend également un message de A pour lui envoyer une réponse.
De ce fait, JMS permet aussi de faire de la réception asynchrone de message en utilisant le mécanisme classique en Java des Listeners (design pattern Observateur).
⹠Un MessageListener créera une thread permettant de gérer la réception du message sur disponibilité de celui-ci (cf. figure suivante).
âą Le MessageListener sera attachĂ© au consommateur mais peut ou non ĂȘtre une classe diffĂ©rente de celle qui abrite le consommateur. Il doit implĂ©menter la mĂ©thode onMessage(Message).
JMS Chap #5
28
Exemple de codeRĂ©ception asynchrone dâun message
LâarrivĂ©e dâun message en mode de rĂ©ception asynchrone dĂ©clenche lâappel de la mĂ©thode onMessage :
JMS Chap #5
Courtier MessageListener
setMessageListener
onMessage
Consommateur
onMessage
29
Exemple de codeRĂ©ception asynchrone dâun message
Cette technique évite le polling et améliore les performances :
âą Le polling signifie que lâon va rĂ©guliĂšrement scruter lâarrivĂ©e dâune donnĂ©e.
âą Câest un mĂ©canisme classique sur les drivers par exemple, mais trĂšs gourmand en temps :
- soit lâintervalle de scrutation est faible et lâon perd du temps Ă vĂ©rifier une donnĂ©e absente,
- soit lâintervalle est long et on perd la donnĂ©e.
âą Par contre, cette technique asynchrone gĂ©nĂšre une thread de rĂ©ception pour la mĂ©thode onMessage et nĂ©cessite de faire attention Ă lâaccĂšs des variables partagĂ©es au thread de rĂ©ception et au thread principal (cf. cours sur les problĂ©matiques de synchronisation).
JMS Chap #5
30
Exemple de codeRĂ©ception asynchrone dâun message
Dans le code suivant, on suppose que la classe qui lance la rĂ©ception implĂ©mente Ă©galement la mĂ©thode onMessage dĂ©finie dans lâinterface MessageListener :
Le code suivant sera déclenché dans le MessageListener sur réception du message :
JMS Chap #5
Queue queue = session.createQueue(âMyQueueâ);MessageConsumer receiver = session.createConsummer(queue);
//On doit attacher le MessageListener avant de démarrer la connexionreceiver.setMessageListener(this);connexion.start();
//Le code suivant permet de vĂ©rifier quelle thread est utilisĂ©eSystem.out.println(âThread lancementâ + Thread.currentThread().getId());
//Code pour attente bloquante permettant de ne pas fermer la connexion
public void onMessage(Message message) {//Affiche le contenu du messageSystem.out.println(((TextMessage) message).getText());//VĂ©rifie que la thread utilisĂ©e est bien diffĂ©rente de celle de lancementSystem.out.println(âThread onMessageâ + Thread.currentThread().getId());
}
31
Exemple de codeRĂ©ception asynchrone dâun message
Attention :
⹠Une session ne peut comporter que des consommateurs synchrones ou que des consommateurs asynchrones dotés de MessageListener.
âą Il nâest pas possible de mĂ©langer deux modes de rĂ©ception dans la mĂȘme session : on crĂ©era alors deux sessions.
JMS Chap #5
32
Exemple de codeRetour sur les connexions et sessions
Le mĂ©canisme de crĂ©ation de connexion et de session est parfaitement identique pour la crĂ©ation et la rĂ©ception de messages (cf. figure suivante). Il suppose deux points dâentrĂ©e :
âą La fabrique de connexions (ConnectionFactory).
âą Le nom de la destination (Queue ou Topic).
La ConnectionFactory crée une connexion qui crée à son tour une ou plusieurs sessions.
Un MessageProducer ou un MessageConsumer nâa dâexistence quâĂ lâintĂ©rieur dâune session. Il est spĂ©cifique Ă une destination. On voit simplement que pour plusieurs Topic dâintĂ©rĂȘt on doit crĂ©er plusieurs consommateurs (resp. producteurs), car les consommateurs sont liĂ©s Ă une seule destination.
Un consommateur peut naturellement recevoir plusieurs messages, de mĂȘme quâun producteur peut en produire plusieurs dans la mĂȘme session.
Une session peut créer plusieurs consommateurs et plusieurs producteurs.
JMS Chap #5
33
Exemple de codeRetour sur les connexions et sessions
Deux points dâentrĂ©e dans le systĂšme JMS : les destinations et les fabriques de connexions :
JMS Chap #5
ConnectionFactory
Connection
Session
Destination
MessageProducer+
MessageConsumer+
34
Exemple de codeRetour sur les connexions et sessions
Pourquoi utiliser plusieurs connexions ?
âą Une connexion est liĂ©e Ă une machine et un port sur lequel Ă©coute le courtier. Lâutilisation de plusieurs connexions permet de se connecter Ă plusieurs courtiers, par exemple pour faire de la redondance.
âą Une connexion est Ă©galement attachĂ©e Ă une identification, typiquement utilisateur et mot de passe. Les implĂ©mentations de JMS les plus complĂštes permettent de restreindre lâaccĂšs Ă certaines ressources telles que des Topic ou des Queue pour certains utilisateurs. On utilisera Ă©ventuellement plusieurs connexions pour diffĂ©rents utilisateurs.
JMS Chap #5
35
Exemple de codeRetour sur les connexions et sessions
Pourquoi utiliser une ConnectionFactory ?
âą Le concept de fabrique (Factory) est usuel en Java. Il permet dans notre cas de crĂ©er des connexions personnalisĂ©es, avec des paramĂštres prĂ©dĂ©finis tels que l'adresse du courtier ou lâutilisateur Ă utiliser.
âą De plus, on stocke cette fabrique dâordinaire dans un annuaire dâentreprise ou dans un annuaire applicatif au travers de lâinterface JNDI qui a Ă©tĂ© prĂ©sentĂ©e dans ce cours. La fabrique permet alors aux clients JMS de disposer dâune configuration prĂ©-Ă©tablie puisque la fabrique contient les paramĂštres dâinitialisation.
âą Voici un exemple de code utilisant lâimplĂ©mentation JMS Joram pour obtenir une connexion prĂ©configurĂ©e en accĂ©dant Ă lâannuaire propriĂ©taire de Joram sur le port 16400 en local :
JMS Chap #5
36
Exemple de codeRetour sur les connexions et sessions
Pourquoi utiliser une ConnectionFactory ?
JMS Chap #5
Properties properties = System.getProperties();properties.put(âjava.naming.factory.initialâ, âfr.dyade.aaa.jndi2.client.NamingContextFactoryâ);properties.put(âjava.naming.factory.hostâ, âlocalhostâ);properties.put(âjava.naming.factory.portâ, â16400â);
InitialContext jndi = new InitialContext();ConnectionFactory fabriqueDeConnexion = (ConnectionFactory) jndi.lookup(âfabriqueâ);Connection connexion = fabriqueDeConnexion.createConnection();
37
Exemple de codeRetour sur les connexions et sessions
Pourquoi utiliser une ConnectionFactory ?
âą LâAPI JMS prĂ©cise que les ConnectionFactory doivent ĂȘtre Referenceable et Serializable afin de pouvoir les stocker dans un annuaire JNDI. Ce ne sont toutefois pas les seuls objets Ă figurer dans lâannuaire : les Queue et Topic peuvent Ă©galement ĂȘtre dĂ©couverts ou configurĂ©s dans un annuaire. Ainsi lors du dĂ©ploiement dâune application, un client pourra obtenir, dans un annuaire, la liste des destinations quâil doit adresser, sans configuration locale et statique.
âą Lâutilisation de JNDI est Ă©galement utile pour permettre dâinterchanger les implĂ©mentations de JMS : en effet, comme la ConnectionFactory comporte tous les paramĂštres dâinitialisation, lâobtention dâun tel objet permet un code identique quelle que soit lâimplĂ©mentation :
JMS Chap #5
ConnectionFactory factory = contexte.lookup(âreferenceâ); (...)
38
Exemple de codeRetour sur les connexions et sessions
Pourquoi utiliser plusieurs sessions ?
âą La session gĂšre les paramĂštres de qualitĂ© de service des messages, ainsi que les Queues temporaires, les transactions, lâordre chronologique des messages, la rĂ©ception synchrone ou asynchrone.
âą Pour les transactions, la session servira de dĂ©limitation, elle permettra dâassurer lâordre des messages Ă lâintĂ©rieur dâune mĂȘme session. Elle assurera Ă©galement la gestion des accusĂ©s de rĂ©ception des messages.
âą Des sessions trop longues peuvent typiquement gĂ©nĂ©rer des attentes dâaccusĂ©s de rĂ©ception des messages et dĂ©tĂ©riorer les performances. Par contre il est important de comprendre quâune session peut comporter Ă la fois des producteurs et des consommateurs de messages, qui peuvent envoyer un nombre quelconque de messages.
JMS Chap #5
39
Exemple de codeUtilisation de hiérarchies de Topic
La plupart des implĂ©mentations JMS offrent des hiĂ©rarchies de Topic comme le montre la figure suivante. Toutefois, la syntaxe de filtrage des Topic ou la crĂ©ation de lâarborescence diffĂšre.
En supposant que le filtrage se fait par des expressions rĂ©guliĂšres, il est ainsi possible de sâabonner Ă /Sports/* pour recevoir les Topic fils : Sports, Footbal, Tennis, PSG, OM.
Ceci peut ĂȘtre utile pour faire des diffusions plus ou moins ciblĂ©es, ou des communications de groupes : si chaque application Ă©coute sur un Topic particulier, on peut les regrouper pour faire une diffusion gĂ©nĂ©rale.
JMS Chap #5
Sports
Footbal Tennis
PSG OM
/Sports/*
/Sports/Footbal/PSG
40
Nature des messages
Un message JMS est constitué de plusieurs parties :
⹠Le corps du message qui véhicule les informations.
⹠Les propriétés du message (une liste de paires attribut/valeur).
âą Lâen-tĂȘte, utilisĂ© pour les informations de qualitĂ© de service et le routage, ainsi que le transport des propriĂ©tĂ©s associĂ©s au message.
JMS Chap #5
41
Nature des messagesLe corps du message
JMS fournit plusieurs natures de messages, permettant différents codages des informations (marshalling) : TextMessage, BytesMessage, MapMessage, StreamMessage, ObjectMessage.
TextMessage :
âą Un TextMessage transporte du texte et peut utiliser les nouvelles classes dâencodage de caractĂšre java.nio dans toutes les langues. Un message de texte peut Ă©galement transporter un contenu au format XML. Ceci permet par exemple Ă JMS de servir de protocole sous-jacent Ă SOAP. Ceci permet Ă©galement dâaccorder la syntaxe du message entre les deux correspondants en utilisant par exemple un schĂ©ma XML pour vĂ©rifier la conformitĂ© de la syntaxe du texte.
âą CrĂ©ation dâun message par la session - la chaĂźnes de caractĂšres est passĂ©e en argument :
JMS Chap #5
TextMessage messageTexte = session.createTextMessage(âhelloâ);
42
Nature des messagesLe corps du message
âą RĂ©ception dâun message
âą On remarquera quâon ignore la nature dâun message Ă rĂ©ception, et que lâon ne peut filtrer sur la nature du message. Il faut donc concevoir lâapplication afin dâutiliser toujours le mĂȘme type de message pour une destination donnĂ©e ou prĂ©voir un aiguillage Ă lâaide de instanceof.
BytesMessage :
âą Un BytesMessage transporte des donnĂ©es binaires. Câest utile pour les images, les fichiers binaires, et de maniĂšre gĂ©nĂ©rale tous les attachements que lâon connaĂźt pour les emails.
âą En effet, JMS peut ĂȘtre considĂ©rĂ© comme un systĂšme dâĂ©change dâemails entre applications et non entre personnes.
JMS Chap #5
TextMessage messageTexte = (TextMessage) receiver.receive();System.out.println(messageTexte.getText());
43
Nature des messagesLe corps du message
⹠On peut également transporter des messages encodés dans tout systÚme de format binaire, tels que ASN1 trÚs utilisés en télécommunications et en cryptographie (il est plus compact que XML).
âą Exemple dâutilisation de BytesMessage :
âą Le problĂšme ici est que lâon ne connaĂźt pas forcĂ©ment la taille du buffer envoyĂ© ! On peut y remĂ©dier en mettant une propriĂ©tĂ© dans lâen-tĂȘte du message. Les propriĂ©tĂ©s seront dĂ©taillĂ©es au paragraphe suivant.
⹠Le récepteur du message pourra alors allouer le tampon de la taille voulue :
JMS Chap #5
BytesMessage outMessage = session.createBytesMessage();byte[] buffer = readFile(âphoto.jpgâ);outMessage.writeBytes(buffer);
outMessage.setIntProperty(âsizeâ, buffer.length);
44
Nature des messagesLe corps du message
MapMessage :
âą Un MapMessage est utilisĂ© pour envoyer un dictionnaire de clĂ©s et de valeurs. Il se marie facilement avec un ResultSet provenant dâun base de donnĂ©es, et fournit les mĂȘmes types natifs (int, float, etc...).
JMS Chap #5
Message message = consumer.receive();//On teste le type de messageif(message instanceof BytesMessage) {
//On rĂ©cupĂšre la taille dans une propriĂ©tĂ©int size = inMessage.getIntProperty(âsizeâ);
//On alloue un tampon adaptébyte[] replyBytes = new byte[size];
((BytesMessage) message).readBytes(replyBytes)(...)
}
MapMessage message = session.createMapMessage();message.setInt(âQuantityâ, 4);message.setStringProperty(âitemTypeâ, âScreenâ);
45
Nature des messagesLe corps du message
En conclusion, JMS permet une grande variĂ©tĂ© de formats dâĂ©change et ne prĂ©juge en rien sur la maniĂšre dont ces donnĂ©es seront exploitĂ©es.
Chaque client JMS peut modifier aisĂ©ment le contenu sans recompilation, et sans adhĂ©rer Ă une syntaxe stricte dâinterface. Câest un des succĂšs de JMS puisquâil permet Ă deux applications de communiquer avec un minimum de modifications.
En contrepartie, il reste Ă la charge du dĂ©veloppeur de sâassurer que les types de messages Ă©changĂ©s sont cohĂ©rents, ainsi que la syntaxe de leur contenu : aucune erreur ne pourra ĂȘtre dĂ©tectĂ©e Ă la compilation. On peut imaginer dâautomatiser ce processus en dĂ©finissant des schĂ©mas XML, par exemple pour valider le contenu texte dâun message.
On notera Ă©galement que lâanalyse syntaxique (parsing) dâun message trop long peut ĂȘtre trĂšs pĂ©nalisante en terme de performances. Enfin, lâencodage du texte en chaĂźnes de caractĂšres sur 16 bits peut ĂȘtre pĂ©nalisent en terme de bande passante, tout comme le protocol SOAP (cours suivant).
JMS Chap #5
46
Nature des messagesPropriétés : filtrage et routage
Lâen-tĂȘte dâun message peut contenir des propriĂ©tĂ©s qui peuvent ĂȘtre utilisĂ©es pour filtrer les messages ou les re-router vers dâautres destinataires.
Les propriĂ©tĂ©s sont des couples (nom, valeur) utilisables par le systĂšme ou lâapplication pour sĂ©lectionner les messages ou les destinataires. Ceci convient naturellement Ă la fois aux Queues et aux Topic.
Les propriĂ©tĂ©s peuvent ĂȘtre des types natifs Java comme boolean, byte, short, int, long, float, double et String.
Le filtrage des messages par propriĂ©tĂ© (cf. figure suivante) permet de ne pas avoir Ă ouvrir le contenu dâun message avant de le router, et ainsi de ne pas faire jouer les accusĂ©s de rĂ©ception. Il sâappuie sur une syntaxe proche de SQL 92 afin de dĂ©finir des expressions boolĂ©ennes sur les propriĂ©tĂ©s :
JMS Chap #5
(Country = âUKâ) OR (Country = âUSâ) OR (Country = âFRâ)age >= 15 AND age <= 19
47
Nature des messagesPropriétés : filtrage et routage
Filtrage des messages par propriétés :
JMS Chap #5
Message
MessageMessage
MessageMessage
Message
Message
Message
Message
Message
Message
Message
Message
Couleur = 'grise' Couleur = 'rouge'
48
Nature des messagesPropriétés : filtrage et routage
Attention : les chaĂźnes sont notĂ©es en SQL avec de simples quotes : âchaineâ.
Envoi du message :
A lâarrivĂ©e, le filtrage est attachĂ© au consommateur, et non sur la mĂ©thode de rĂ©ception :
On peut Ă©galement utiliser la rĂ©ception asynchrone en attachant un MessageListener au consommateur filtrĂ©. Par contre dans tous les cas, le filtre nâest plus modifiable aprĂšs crĂ©ation du consommateur.
De nombreux usages dĂ©coulent du filtrage : le routage dans un centre dâappels en fonction du motif dâappel, le routage dâinformation de bourse en fonction de lâimportance du message, etc...
JMS Chap #5
TextMessage message = session.createText(âbonjourâ);message.setStringProperty(âcouleurâ, âgriseâ);publisher.send(message);
MessageConsumer receiver = session.createConsumer(topic, âcouleur=âgriseââ);connexion.start();TextMessage mess = (TextMessage) receiver.receive();
49
Nature des messagesEn-tĂȘte des messages et QoS
Lâen-tĂȘte du message vĂ©hicule des informations de QualitĂ© de Service (QoS). Toutefois rien nâoblige une implĂ©mentation Ă mettre en oeuvre tous ces aspects.
De plus, selon les implémentations les différents niveaux de définition seront implémentés ou non :
âą lors de lâenvoi du message :
⹠lors de la création du message :
⹠ou attachés au producteur pour tous ses messages :
JMS Chap #5
producer.send( Message message,int deliveryMode, //persistant ou nonint priority, //prioritélong timeToLive //durée de vie
message.setDeliveryMode(DeliveryMode.PERSISTENT);
producer.setTimeToLive(10000);
50
Nature des messagesEn-tĂȘte des messages et QoS
On trouve plusieurs attributs dans lâen-tĂȘte du message (mĂ©thode getXXX du message) :
JMS Chap #5
Champ dâen-tĂȘte Fonction DĂ©fini
JMSDestination La Queue ou le Topic. Automatiquement lors du send.
JMSDeliveryMode Définit si le message est persistant ou non. Renseigné par code.
JMSExpirationDĂ©finit le temps de rĂ©tention dâun message
persistant.Renseigné par code lors du send.
JMSMessageID Identifiant unique du message. Généré par le courtier.
JMSPriority Priorité du message.Permet de traiter certains messages en priorité.
Nâa quâune portĂ©e sur la session.
JMSTimestamp Estampille. Généré par le courtier.
JMSCorrelationIDCorrélation entre deux messages. Utilisé pour les
requĂȘtes-rĂ©ponses.Par code.
JMSReplyToChamp optionnel permettant de donner une
adresse de retour.Par code.
JMSType Nature du contenu du message (Text, Bytes, etc.) Courtier.
JMSRedeliveredIndique sâil sâagit dâune nouvelle tentative dâenvoi
sur Ă©chec dâacquittement.Courtier.
51
Nature des messagesEn-tĂȘte des messages et QoS
Le mode de livraison (deliveryMode) peut ĂȘtre persistant ou non : DeliveryMode.PERSISTANT ou NON_PERSISTANT. En pratique, les messages seront quasiment toujours persistants, par contre on pourra jouer sur leur durĂ©e de vie (TimeToLive) exprimĂ©e en milliseconde (ms). Une durĂ©e de vie de 0 indique par convention que le message nâexpirera jamais.
Si lâenvoi dâun message persistant Ă©choue, celui-ci sera renvoyĂ© dans le mode point Ă point ou le mode publication/abonnement avec abonnement durable. Ainsi on peut ĂȘtre sur quâun message persistant ne sera supprimĂ© que sur accusĂ© de rĂ©ception du consommateur.
Le renvoi du message peut ĂȘtre dĂ©tectĂ© en inspectant le champ JMSRedelivered. En effet, JMS assure la livraison du message en rĂ©itĂ©rant celle-ci sur erreur.
JMS Chap #5
52
Fiabilité de la communication
Une des motivations de lâutilisation de JMS va ĂȘtre la fiabilitĂ© du transport des informations.
JMS Chap #5
53
Fiabilité de la communicationAbonnés durables
Lors de la rĂ©ception dâun message en mode publication/abonnement, le client doit ĂȘtre prĂ©sent, il ne sera pas livrĂ© de nouveau. En effet, comme le nombre de destinataires est dynamique et non connu Ă lâavance, il est impossible de savoir si tout le monde a reçu le message et de renvoyer celui-ci si cela nâest pas le cas.
Pour fiabiliser la rĂ©ception des messages et ĂȘtre sĂ»r que tous les clients qui le souhaitent on reçu le message, ceux-ci doivent souscrire un abonnement durable (durable subscribers). Cet abonnement permettra au courtier de sâassurer quâils ont reçu le message par acquittement, et de leur renvoyer si ce nâest pas le cas.
Pour cela, il suffit dâune part de donner un identifiant Ă la connexion, dâautre part de crĂ©er le consommateur en spĂ©cifiant un nom dâabonnement :
JMS Chap #5
54
Fiabilité de la communicationAbonnés durables
On vérifiera alors que si le message est persistant et envoyé en mode publication/abonnement, le client pourra le recevoir ultérieurement sur reconnexion.
Pour supprimer lâabonnement, il faut se connecter avec le mĂȘme clientID, et utiliser la mĂ©thode unsubscribe avec le nom dâabonnement :
JMS Chap #5
ConnectionFactory qcf = ... // selon implĂ©mentationConnection qc = qcf.createConnection(âUtilisateurâ, âMotDePasseâ);qc.setClientID(âMonAppliJMSâ);Session s = qc.createSession(false, Session.AUTO_ACKNOWLEDGE);Topic topic = s.createTopic(âDeveloppement.Durableâ);MessageConsumer receiver = s.createDurableSubscriber(topic,âAbonnement1â);receiver.setMessageListener(new Receiver());qc.start();
qc.setClientID(âMonAppliJMSâ);(...)s.unsubscribe(âAbonnement1â);
55
Fiabilité de la communicationSéquence des messages
Les messages transitent par un nombre quelconque de courtiers. De plus, il peuvent ĂȘtre associĂ©s Ă des prioritĂ©s diverses, et le courtier peut ĂȘtre plus ou moins chargĂ©.
Les implĂ©mentations vont autant que possible respecter la chronologie des messages, mais certains ordinateurs peuvent avoir des horloges non synchronisĂ©es, etc... Il est donc possible quâen bout de chaĂźne il y ait une inversion de lâordre des messages. Il est important dans la conception dâenvisager le cas dâinversion des messages et soit de sâassurer que cela nâa pas dâimpact soit dây remĂ©dier.
Lâinversion des messages ne peut arriver dans un mode RPC pour un mĂȘme producteur, car il doit attendre le retour de son appel avant dâeffectuer le suivant.
JMS Chap #5
56
Fiabilité de la communicationAcquittement des messages
Envoi dâun message une fois et une seule :
⹠Dans certaines applications il est trÚs important que le message soit envoyé une fois et une seule, par exemple pour un retrait bancaire.
âą Si lors dâun retrait dâargent, le serveur de banque renvoie une erreur de connexion, il peut ĂȘtre impossible de savoir si elle a eu lieu avant ou aprĂšs la prise en compte de la transaction.
âą Si on renvoi le message de dĂ©bit, cela peut ĂȘtre au dĂ©triment de lâutilisateur qui sera furieux dâĂȘtre dĂ©bitĂ© deux fois, et si on ne le renvoie pas, cela peut porter prĂ©judice Ă la banque.
âą Obtenir que le message soit envoyĂ© une, et une seule, fois peut ĂȘtre trĂšs important. Pour cela on utilisera des mĂ©canismes sophistiquĂ©s dâacquittement. (cf. ci-aprĂšs).
JMS Chap #5
57
Fiabilité de la communicationAcquittement des messages
Acquittement gérés par le systÚme :
âą Une des maniĂšres dâassurer la fiabilitĂ© est de pouvoir gĂ©rer les accusĂ©s de rĂ©ception. Ils ne sont gĂ©rĂ©s que pour les Queue ou pour les Topic avec des abonnĂ©s durables. Par dĂ©faut on utilise lâattribut Session.AUTO_ACKNOWLEDGE, mais il existe deux autres possibilitĂ©s.
âą AUTO_ACKNOWLEDGE signifie que respectivement lors du send le client JMS sera en attente dâun accusĂ© de rĂ©ception du courtier, et inversement lors du message receive. Si un incident survient, le message nâest pas considĂ©rĂ© comme reçu et peut ĂȘtre renvoyĂ© Ă nouveau pour le courtier. La figure suivante illustre ce concept.
JMS Chap #5
connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
58
Fiabilité de la communicationAcquittement des messages
Le message est supprimé aprÚs accusé de réception :
JMS Chap #5
Producteur
Consommateur
Courtier
JMS
Stockage
1. send
2. stockage
3. OK
5. OK
4. receive
6. suppression
59
Fiabilité de la communicationAcquittement des messages
Acquittement gérés par le systÚme (suite) :
âą En mode automatique, lâacquittement est envoyĂ© aprĂšs chaque message. Si le message est renvoyĂ© Ă nouveau, il portera un drapeau JMSRedelivered dans son en-tĂȘte. Le courtier utilise lâattribut JMSMessageID pour vĂ©rifier que les messages sont envoyĂ©s une fois et une seule.
âą En mode CLIENT_ACKNOWLEDGE, le consommateur a la responsabilitĂ© dâacquitter le message Ă tout moment de la session :
âą Il peut ainsi acquitter un paquet de messages dâun coup, ce qui peut optimiser les performances. Si jamais il y a une exception avant lâacquittement global, tous les messages seront renvoyĂ©s au prochain receive, mĂȘme ceux qui avaient dĂ©jĂ Ă©tĂ© reçus.
JMS Chap #5
message.acknowledge();
60
Fiabilité de la communicationAcquittement des messages
Acquittement applicatif de messages :
âą Le modĂšle de communication JMS est asynchrone, par envoi de requĂȘtes sans rĂ©ponses. Il peut ĂȘtre souhaitable de recevoir une rĂ©ponse de maniĂšre diffĂ©rĂ©e, mais comment alors lâapparier avec la requĂȘte initiale ?
⹠Il existe plusieurs éléments de réponse à ce problÚme :
- Utiliser lâattribut JMSCorrelationID dans lâen-tĂȘte du message.
- Utiliser des objets QueueRequestor;
- Utiliser des Queues temporaires de réponse (TemporaryQueue).
âą Lâutilisation des QueueRequestor est simple : cet objet se substitue au MessageProducer et au MessageConsumer, et va rester bloquĂ© en attente sur la mĂ©thode request :
JMS Chap #5
61
Fiabilité de la communicationAcquittement des messages
Acquittement applicatif de messages :
âą Pendant ce temps, son interlocuteur va devoir rĂ©pondre de maniĂšre particuliĂšre. Il va extraire de lâen-tĂȘte du message lâadresse de retour et Ă©galement apparier les numĂ©ros de corrĂ©lation de la requĂȘte et de la rĂ©ponse.
JMS Chap #5
requestor = new QueueRequestor(session, queue);connection.start();TextMessage requete = session.createTextMessage();msg.setText(âHello World!â);Message reponse = requestor.request(requete);
String requete = messageRequete.getText();Queue replyQueue = (Queue) messageRequete.getJMSReplyTo();if( replyQueue != null ) {
//renvoi la rĂ©ponseTextMessage reponse = session.createTextMessage(âReponseâ);reponse.setJMSCorrelationID(aMessage.getJMSMessageID());MessageProducer producteur...producteur.send(replyQueue, reponse);
}
62
Fiabilité de la communicationAcquittement des messages
Acquittement applicatif de messages :
âą Tout se passe comme si vous demandiez un document officiel Ă une administration en incluant dans votre courrier une enveloppe de retour Ă votre adresse.
⹠Le correspondant peut récupérer la destination de retour dans le champ JMSReplyTo du message.
JMS Chap #5
63
Fiabilité de la communicationUtilisation des transactions
Les transactions ne sont pas rĂ©servĂ©es aux bases de donnĂ©es. La motivation dâune transaction en JMS est de permettre de rendre des messages solidaires au sein dâune session afin de sâassurer quâils sont tous envoyĂ©s ou reçus, ou aucun dâentre eux.
Dans lâexemple de requĂȘte-rĂ©ponse prĂ©cĂ©dent, deux opĂ©rations interviennent sur lâinterlocuteur :
âą La rĂ©ception de la requĂȘte.
âą Lâenvoi de la rĂ©ponse.
Il est souhaitable que la rĂ©ponse nâait lieu que si la requĂȘte a Ă©tĂ© bien reçue, et de mĂȘme que la requĂȘte bien reçue induise une rĂ©ponse. On peut donc lier ces deux opĂ©rations dans une mĂȘme transaction (cf. figure suivante). La dĂ©limitation de la transaction sera le dĂ©but et la fin de la session.
JMS Chap #5
64
Fiabilité de la communicationUtilisation des transactions
ConcrĂštement, dans le flux de circulation dâinformation, on voudra par exemple sâassurer de dĂ©clencher une livraison de produit uniquement si lâon a reçu le paiement, et inversement mettre obligatoirement en livraison toutes les commandes payĂ©es.
JMS Chap #5
Transaction
envoi paiement livraison produit
65
Fiabilité de la communicationUtilisation des transactions
La transaction et attachĂ©e Ă la session lors de sa crĂ©ation (boolĂ©en true pour indiquer la prĂ©sence dâune transaction) :
La session peu alors procéder à des émissions et réceptions de messages. La transaction sera validée lors du commit et invalidée lors du rollback.
Si une exception survient entre 2. et 3., la session ne sera pas validĂ©e, le message de paiement nâaura pas Ă©tĂ© reçu et lâordre de livraison nâaura pas Ă©tĂ© envoyĂ©.
JMS Chap #5
connexion.createSession(true, Session.AUTO_ACKNOWLEDGE);
1. try {2. messagePaiement = consommateur.receive();3. producteur.send(messageLivraison);4. session.commit();5. } catch(Exception e) {6. session.rollback();7. }
66
Conclusion
JMS est une API définie dans Java EE qui permet deux modes de communication : par messages ou par événements.
Le mode par Ă©vĂ©nement permet une diffusion fiable dâune information vers plusieurs interlocuteurs.
LâAPI JMS comporte des mĂ©canismes sophistiquĂ©s de gestion de la qualitĂ© de service (transaction, acquittement, prioritĂ©s), mais cela ne dispense pas de valider chaque implĂ©mentation pour connaĂźtre le rĂ©el niveau de qualitĂ© de service mis en oeuvre.
Les communications transitent en général par un stockage intermédiaire qui peut induire des aléas de performance en fonction de la charge du courtier.
Certains éditeurs introduisent des solutions hautement disponibles pour augmenter la fiabilité en couplant plusieurs courtiers.
JMS Chap #5
67