Si conclude con questa puntata la mini serie di articoli dedicati a JMS. Questa quarta e ultima parte è dedicata alla presentazione di un’applicazione che utilizza congiuntamente i modelli PTP e Publisher/Subscriber, alla spiegazione delle destinazioni temporanee per poi concludere con una breve panoramica sugli EJB Message Driven.
Si conclude con questa puntata la mini serie di articoli dedicati a JMS. Questa quarta e ultima parte è dedicata alla presentazione di un’applicazione che utilizza congiuntamente i modelli PTP e Publisher/Subscriber, alla spiegazione delle destinazioni temporanee per poi concludere con una breve panoramica sugli EJB Message Driven.
Per riepilogare i concetti visti nelle puntate precedenti([JMS1],[JMS2],[JMS3]),si presenta un’applicazione JMS che ha lo scopo di gestire la vendita di biglietti di partite di football americano e le relative applicazioni client (potenziali acquirenti). L’applicazione è costituita da tre classi:
TicketMessage: è la classe che incapsula il biglietto della partita da vendere/acquistare
TicketSeller: è la classe responsabile di vendere i biglietti delle partite
TicketBuyer: è la classe in grado di comprare i biglietti delle partite d’interesse
Figura 1: ecco la didascalia
L’applicazione JMS che vende in biglietti e i relativi client acquirenti.
La classe TicketMessage incapsula la descrizione del biglietto e per ogni sua proprietà mette a disposizione i relativi metodi get e set:
public class TicketMessage implements java.io.Serializable {
// valore dello stato del biglietto in vendita
public static final String INSALE = "TICKET IN SALE...";
// valore dello stato del biglietto comprato
public static final String SOLD = "! SOLD !";
/** ID del biglietto */
private int id;
/** Stadio */
private String stadium;
/** Nome della squadra che gioca in casa */
private String home;
/** Nome della suadra ospite */
private String visitors;
/** Data della partita */
private String date;
/** Stato del biglietto : INSALE / SOLD */
private String status;
/** Data di messa in vendita */
private String insaleDate;
/** Data di effettiva vendita del biglietto */
private String soldDate;
/** ID del compratore */
private String userId;
. . .
La vendita dei biglietti
La situazione di vendita è un tipico caso di modellazione Publish /
Subscriber:
il venditore, rappresentato dalla classe TicketSeller,
pubblica presso un topic(MokaTopicSell) i biglietti da vendere. Chiunque si
dichiari subscriber di quel topic è in grado di ricevere gli annunci di
vendita.
I biglietti da vendere sono contenuti in un array di oggetti di classe
TicketMessage:
private static final TicketMessage arrayTickets[] = {
new TicketMessage(0, "Soldier Field", "Pittsburgh", "Chicago", "01/10/02"),
new TicketMessage(1, "Coliseum", "Oakland", "Chicago", "03/11/02"),
new TicketMessage(2, "Heinz Field", "Pittsburgh", "Cincinnati", "10/11/02"),
. . .
}
La classe TicketSeller provvede a metterli in vendita, pubblicandoli a istanti temporali definiti e se non sono stati nel frattempo già venduti, presso il MokaTopicSell:
public class TicketSeller extends JFrame implements MessageListener {
private void publishTicketsToSell(TopicSession topicSession) {
this.message = topicSession.createObjectMessage();
. . .
for(int i = 0; i < arrayTickets.length; ++i) {
if(arrayTickets[i].getStatus().equals(TicketMessage.INSALE)) {
// se il biglietto è in vendita
try {
Thread.sleep(5000);
// aspetto…
} catch(InterruptedException ie) {
. . .
}
// preparo il messaggio da vendere
arrayTickets[i].setInsaleDate(new java.util.Date().toString());
message.setObject(arrayTickets[i]);
message.setJMSReplyTo(tempOrderTopic);
topicPublisher.publish(message);
// lo pubblico presso il MokaTopicSell
. . .
I compratori sono le applicazioni JMS che si dichiarano subscriber presso il MokaTopicSell.
Fino ad ora, niente di nuovo rispetto a quello già visto nelle puntate precedenti; passiamo a vedere come implementare la parte relativa all’acquisto dei biglietti. L’acquisto dei biglietti Tutti i client JMS in ascolto sul topic MokaTopicSell sono potenziali acquirenti. Quando un client JMS decide di comprare un biglietto deve comunicare con il solo venditore inoltrando la richiesta d’acquisto. Utilizzo delle Temporary Destination Fino ad adesso si sono utilizzate destinazioni gestite come Administered Object, cioè create/configurate/rimosse dal Provider JMS mediante o l’opportuno tool di amministrazione o lo specifico file di configurazione. Le API JMS permettono di creare destinazioni dette temporanee (TemporaryQueue e TemporaryTopic) la cui durata è strettamente legata a quella della connessione tramite la quale sono state create. Queste destinazioni sono create dinamicamente dall’applicazione utilizzando i metodi createTemporaryQueue() della classe QueueSession e createTemporaryTopic() della classe TopicSession. Le destinazioni temporanee sono ad uso privato dell’applicazione JMS che le ha create e quindi gli altri client JMS non ne hanno visibilità. Se si chiude la connessione da cui si è creata la destinazione temporanea, anch’essa viene chiusa ed il suo contenuto viene perso. Nell’applicazione in esame il TicketSeller crea quindi un temporary topic sul quale si predisporrà a ricevere le richieste di acquisto
Gli acquirenti quindi potranno inviare le richieste di acquisto al topic temporaneo in modo tale che esse siano visibili al solo venditore e non agli altri acquirenti. Resta da capire come è possibile indicare al consumatore la destinazione del produttore.
Il campo JMSReplyTo
Il campo JMSReplyTo, appartiene all’header del messaggio JMS, permette al consumatore di un messaggio di ricavarsi la destinazione a cui rispondere. Nel nostro esempio, l’acquirente ricava dalla lettura di tale campo la destinazione del venditore verso il quale inoltrare la richiesta d’acquisto. Per attuare questo semplice meccanismo di request/reply, il client Seller, prima di inviare il messaggio, valorizza il campo JMSReplyTo con il valore del temporary topic precedentemente creato
ed effettua l’opportuno downcast alla specifica tipologia della destinazione
Topic buytopic = (Topic)dest;
per poi provvedere all’invio del messaggio
publisher = this.topicSession.createPublisher(buytopic);
// creo il publisher
publisher.publish(msg);
// invio il messaggio d’acquisto
Il campo JMSReplyTo permette di fatto un “routing applicativo” di messaggi che può essere particolarmente utile in tipici contesti di workflow in cui è necessario specificare le destinazioni da utilizzare a secondo dello stato e del punto particolare del processo. Il metodo decideToBuy() conterrà la logica responsabile dell’acquisto del biglietto e verrà invocato ogni qualvolta si riceve un annuncio di vendita.
public void onMessage(javax.jms.Message message) {
decideToBuy(message);
}
Tale metodo effettua un semplice confronto tra la squadra che gioca in casa indicata nel biglietto da comprare (ticket.getHome()) e la squadra preferita dell’utente (this.teamName)
Acquisto dei biglietti: utilizzo del topic temporaneo.
Utilizzo delle code in modalità Point to point
Mentre la modellazione di vendita dei biglietti è un tipico caso di relazione 1 a molti (1 venditore - N acquirenti), l’acquisto dei biglietti è di fatto una relazione 1 a 1 tra acquirente e venditore e quindi un tipico scenario Point-to-Point. Il TicketSeller effettua quindi una lookup sulla coda MokaQueueBuy e si pone in ricezione di eventuali messaggi di richiesta d’acquisto dichiarandosi Listener della destinazione.
L’utilizzo del campo JMSReplyTo dell’header del messaggio è analogo al caso precedente. Il TicketSeller specifica in fase di publishing la coda alla quale inviare la risposta
message.setJMSReplyTo(this.queue);
mentre il TicketBuyer, una volta ricevuto il messaggio,
legge il campo JMSReplyTo e provvede ad inviare il messaggio di richiesta
d’acquisto
Queue buyQueue = (Queue)dest;
// creo il QueueSender
QueueSender queueSender = this.queueSession.createSender(buyQueue);
queueSender.send(msg);
// invio il messaggio d’acquisto
Figura 3: ecco la didascalia
Acquisto dei biglietti: utilizzo della Queue
Identificazione dell’acquirente
Una volta implementata la parte relativa all’acquisto dei biglietti, resta da risolvere il problema dell’identificazione dell’acquirente tra gli N subscriber attivi. Per fare questo è possibile utilizzare il campo JMSCorrelationID, che fa parte dell’header del messaggio JMS e può essere utilizzato per vari scopi applicativi: nella nostra applicazioni nel JMSCorrelationID verrà inserito l’username del cliente prima di inviare il messaggio d’acquisto.
// Setto la username come correlationID
msg.setJMSCorrelationID(this.userName);
// invio il messaggio d'acquisto
queueSender.send(msg);
In alternativa si sarebbe potuto decidere di specificare l’identità dell’acquirente con un message property
Infine si noti come, inserendo nel JMSCorrelationID del messaggio di risposta lo stesso valore del messaggio ricevuto, tale campo può essere utilizzato anche come correlatore di request e response.
JMS & J2EE
Gli EJB 1.1 (Session e Entity) sono componenti prettamente sincroni che si basano su architettura ORB. Con l’avvento delle specifiche EJB 2.0 hanno fatto la comparsa i Message Driven Beans (MDB), EJB asincroni basati su JMS. I MDB sono una variante degli EJB Session Stateless in grado di processare messaggi JMS; analogamente agli EJB Session Stateless, i MDB non sono in grado di mantenere uno stato conversazionale per uno specifico client e sono gestiti in pool. La caratteristica principale dei MDB è che permettono quindi di consumare in modo asincrono e concorrente messaggi da destinazioni JMS attraverso l’infrastuttura J2EE dell’Application Server. Dato che un MDB non è un componente invocabile da remoto direttamente da un client, non possiede le interfacce Home e Remote, ma implementa invece le interfacce javax.ejb.MessageDrivenBean e javax.jms.MessageListener e deve quindi definire i seguenti metodi:
ejbCreate(): viene invocato quando l’EJB viene creato
ejbRemove(): viene invocato prima che il container rimuova l’EJB
setMessageDrivenContext(MessageDrivenContext ctx): il context è passato all'EJB dopo che è stato istanziato e prima della ejbCreate(). Il context permette di gestire transazioni con i metodi getUserTransaction(), setRollbackOnly(), getRollbackOnly() e per la security con i metodi getCallerPrincipal(), isCallerInRole().
OnMessage(Message msg): tale metodo è sempre invocato in una propria e separata transazione e viene invocato esattamente come avviene per un MessageListener di un normale client JMS.
Il ciclo di vita di un EJB Message Driven è il seguente:
Figura 4: ecco la didascalia
Ciclo di vita di in Message Driven Bean
Differenze tra un’applicazione JMS ed un EJB MD
Dopo questa breve introduzione sui MDB si può passare ad analizzare brevemente le differenze di sviluppo tra un MDB e tra un’applicazione JMS standalone. Nel caso del MDB le operazioni di creazione del message consumer (QueueReceiver/TopicSubscriber), di registrazione del message listener (setMessageListener) e della modalità di acknowledge del messaggio vengono effettuate dall'EJB Container e non sono più a carico dell’applicativo. L'associazione tra il MDB, il ConnectionFactory e la destinazione avvengono a deploy time mediante il deployment descriptor file. Un semplice EJB Message Driven
Vediamo ora un semplice MDB che stampa a video il contenuto dei messaggi che riceve da una coda; esso è costituito da una sola classe di nome SimpleMDB :
public class SimpleMDB implements MessageDrivenBean, MessageListener {
private transient MessageDrivenContext mdc = null;
private Context context;
public SimpleMDB() {
}
public void setMessageDrivenContext(MessageDrivenContext mdc) {
this.mdc = mdc;
}
public void ejbCreate() {
}
public void onMessage(Message message) {
TextMessage msg = null;
try {
if(message instanceof TextMessage) {
msg = (TextMessage) message;
} else {
System.out.println("msg of wrong type: ");
}
Il legame con la destinazione a cui è collegato il MDB non viene definito all’interno del codice ma verrà descritto nei file DD ejb.jar.xml ed il DD vendor-dependent. Nel file standard di deploy ejb-jar.xml si riporta il nome della classe del MDB (<ejb-class>), il tipo di transazione(<transaction-type>), la modalità di acknowledge(<acknowledge-mode>) e la tipologia della destinazione(<destination-type>).
Inoltre, come da specifica EJB, ogni riferimento alle risorse fisiche (per esempio nome della coda) è inserita nel Deployment Descriptor dello specifico Application Server J2EE; nel caso dell’Application Server JBoss è nel file jboss.xml che si associa al MDB la destinazione fisica a cui è collegato.
Nell’esempio riportato il MDB è listener di una destinazione Queue di nome
JNDI queue / MokaQueueMDB. Per completare il processo di creazione del bean è
necessario effettuare il deploy del file contenente il SimpleMDB.class e i due
deployment descriptor;
sempre nel caso si utilizzi JBoss il deploy avviene in modo molto semplice
copiando il jar ottenuto nella directory <JBOSS_HOME>\deploy. Per provare
l’EJB è sufficiente scrivere un client JMS che crei dei messaggi e li invii
alla coda indicata dal nome JNDI queue / MokaQueueMDB. Per complicare
leggermente l’esempio si può pensare di sviluppare un MDB che effettua
l’eco dei messaggi ricevuti dalla coda JMS. Rispetto al caso precedente,
dove il MDB necessitava solo di ricevere messaggi,
adesso è necessario anche inviare ciò che si riceve al mittente del messaggio.
Nel deployment descriptor si deve valorizzare il tag <resource-ref> con la dichiarazione del tipo e del nome della risorsa esterna utilizzata dall’EJB mediante gli elementi <res-ref-name>, <res-type>, e <res-auth>. Nel file jboss.xml si deve specificare, oltre al nome della destinazione da cui ricevere i messaggi, anche il nome JNDI del Queue Connection Factory.
Con questo esempio si conclude la mini-serie dedicata a JMS: lo scopo (spero raggiunto) era di dare una panoramica la più possibile completa di JMS, spaziando dal modello PTP al Publisher/Subscriber, descrivendo la modalità store and forward, i message selector e le destinazioni temporanee fino a concludere con una breve panaramica degli EJB MDB.
Bibliografia
[JMS1]S.Rossini:"JMS-La gestione dei messaggi : la teoria", Mokabyte N.60, Febbraio 2002
[JMS2]S.Rossini:"JMS-La gestione dei messaggi: la pratica", Mokabyte N.61, Marzo 2002 [JMS3]S.Rossini:"JMS-La gestione dei messaggi: terza parte",Mokabyte N.68, 9mbre 2002 [ORJMS] D.A.Chappel,R.M.Haefel - Java Message Service,O’Reilly 2001 [JMSS] JMS Specification (versione 1.0.2b) - http://java.sun.com/products/jms/docs.html [JMST] JMS tutorial - http://java.sun.com/products/jms/tutorial/index.html [EEDOC] J2SDKEE API Documentation [BWLS] P. Gomez, P. Zadrozny – “Java 2 Enterprise Editino with BEA Weblogic Server”, Wrox 2001
MokaByte®
è un marchio registrato da MokaByte s.r.l.
Java®, Jini® e tutti i nomi derivati sono marchi
registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione
anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it