Documentazione Contatti      
Documentazione > Tutorial > Un esempio di JMS con Jboss
Hide
Best Practices
EJB
Frameworks
Howto
J2EE
J2ME and Wireless
J2SE
JSP e Servlet
Java Application Server
Java IDE/Tools
Java Media
Java Security
Java Sys Admin
Java e XML
Java e SQL
OpenSource Java
Patterns
Repository
Tesi
UML
Web Services
Slide
White Paper di jws.it
project management
Eventi
Groovy



Online le slide del Javaday IV


Un giorno le macchine riusciranno...
a risolvere tutti i problemi, ma mai nessuna di esse potra' porne uno. Einstein


MYSQL - Controllo degli accessi



  Visualizza Commenti (0) Aggiungi Commento    
 
Un esempio di JMS con Jboss
By Giulio Rambelli
10 giugno 2005

  Un esempio di JMS con Jboss

Java Message Service è un'API standard facente parte della piattaforma j2ee che può essere usata per accedere a sistemi di messagistica enterprise.
I sistemi di messagistica enterprise consentono lo scambio di messaggi tra applicazioni su una rete.
In questo articolo vado a presentare i passi basilari per implementare una web application, che fa da 'JMS Producer', e una semplice enterprise application che fa da 'JMS Consumer'
All'utente viene presentata una pagina html con un campo di testo e un pulsante submit, cliccando il pulsante verrà chiamata una servlet che invierà il testo scritto nel textfield come jms message ad una applicazione remota, che, ricevuto il messaggio scriverà il testo in un file sul suo File System.
Lo schema classico di una struttura JMS è composto da un lato da un'applicazione che agisce da produttore del messaggio, che può essere una servlet, un ejb o un'applicazione standalone, dall'altro lato è presente uno (il messaggio è destinato ad un solo consumatore-modello Point to Point) o più (il messaggio è destinato ad un range di consumatori o subscribers-modello publish/subscribe) componenti particolari che agiscono da consumatori del messaggio: Message Driven Beans; tra questi è sempre presente una destinazione del messaggio che può essere di tipo Queue(PointToPoint) o Topic(publish-subscribe), per semplicità nell'esempio, che si rifà al modello Point to Point, la Queue sarà situata sulla stessa macchina del ricevente (Consumer).
Un MDB è un EJB a tutti gli effetti, la cui particolarità è che non essendo referenziabile da un'applicazione client che sia in locale o in remoto, non è dotato di interfacce home, remote, local-home o local-remote; da ciò deriva una estrema facilità di implementazione, sia per quanto riguarda il componente stesso sia per quanto riguarda i vari deployment descriptors nei quali deve essere mappato.
Un componente MDB dovrà implementare le interfacce MessageDrivenBean e MessageListener, dovrà inoltre implementare i metodi :

public void setMessageDrivenContext(MessageDrivenContext mdc) throws EJBException {}
public void ejbCreate() {}
public void ejbRemove() {}
public void onMessage(Message message) {}

il metodo public void onMessage(Message message) è quello più interessante dal nostro punto di vista perchè è il metodo che il container richiama sull'MDB quando arriva un messaggio sulla 'Queue' o sul 'Topic' su cui l'MDB stesso è "in ascolto", nel corpo di questo metodo sarà quindi presente tutto il codice che dovrà essere eseguito all'arrivo del messaggio.
Cominciamo con la web application:
La nostra web application sarà strutturata secondo la gerarchia di directories mostrata dalla figura seguente

Fig. 1 Un esempio di JMS con Jboss

La pagina html di benvenuto index.html fornisce un semplice form dotato di textfield in cui scrivere il messaggio di testo che sarà inviato alla servlet ProducerServlet.java :

package com.mydomain;

import java.io.*;
import java.util.*;
import javax.jms.*;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ProducerServlet extends HttpServlet {

Context context = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
Queue queue = null;
QueueSender queueSender = null;
TextMessage message = null;
boolean esito = true;

/**
* uso l'IP di destinazione preso dal file web.xml in cui si trova come init-param per inizializzare
* l'InitialContext e richiamo il metodo getQueueSender().
* in caso di errori forwardo la request ad una pagina di errore
*/
public void init() {
      String destinationIP = getInitParameter("destinationIP");
      Hashtable env = new Hashtable();
      env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,

   "org.jnp.interfaces.NamingContextFactory");
      env.put("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
      env.put(javax.naming.Context.PROVIDER_URL, destinationIP);

      try {
           context = new InitialContext(env);
           getQueueSender();
           context.close();
           } catch(Exception exc) {
           System.out.println(exc.toString());
           esito = false;
      }
}

/**
* prendo la factory da cui ottenere una QueueConnection e la Queue dall'albero JNDI
* inizializzo i vari oggetti che mi serviranno per l'invio del messaggio
* e creo il messaggio stesso (per adesso vuoto).
*/
public void getQueueSender() throws JMSException,NamingException {

    QueueConnectionFactory queueFactory = (QueueConnectionFactory)context.lookup("ConnectionFactory");
        queueConnection = queueFactory.createQueueConnection();
        queueSession = queueConnection.createQueueSession(false,
             javax.jms.Session.AUTO_ACKNOWLEDGE);
        queue = (Queue)context.lookup("queue/A");
        queueSender = queueSession.createSender(queue);
        message = queueSession.createTextMessage();
}

/**
* setto il testo immesso dall'utente al TextMessage e lo invio.
*/
public void sendMsg(String testo) throws JMSException {
     message.setText(testo);
     queueSender.send(queue, message);
}

/**
* prendo il testo immesso dall'utente e se fino adesso tutto è andato bene
* lo passo al metodo sendMsg(String testo) in caso di errori forwardo la request
* ad una pagina di errore, altrimenti ad una pagina in cui comunico l'avvenuto
* accodamento del messaggio.
*/
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException,
                               ServletException {
      String whereToForward = null;
      String testo = request.getParameter("testo");
        if(esito) {
                    try {
                          sendMsg(testo);
                          whereToForward = "success";
                          } catch (JMSException jme) {
                          System.out.println(jme.toString());
                     whereToForward = "failure";
                     }
         } else {
                   whereToForward = "failure";
         }
        System.out.println("whereToForward: " + whereToForward);
        request.getRequestDispatcher("/" + whereToForward + ".jsp").forward(request,response);
}

/**
* richiamo il metodo close() sugli oggetti usati per l'invio del messaggio
*/
public void destroy() {
       try {
            if(queueSender != null)queueSender.close();
            } catch (JMSException jme) {
            System.out.println(jme.toString());
            } finally {
            queueSender = null;
      }
      try {
           if(queueSession != null)queueSession.close();
           } catch (JMSException jme) {
           System.out.println(jme.toString());
           } finally {
           queueSession = null;
      }
      try {
           if(queueConnection != null)queueConnection.close();
           } catch (JMSException jme) {
           System.out.println(jme.toString());
           } finally {
           queueConnection = null;
           }
      }
}


Questa servlet contiene tutta la logica applicativa necessaria ad inviare il messaggio: per prima cosa fondamentale viene creato un InitialContext al cui costruttore viene passato un oggetto della classe Hashtable contenente le chiavi INITIAL_CONTEXT_FACTORY e PROVIDER_URL con i rispettivi valori; questo passo è importante perché il Context che andiamo a creare punta direttamente al Context presente sulla macchina il cui IP viene passato come valore alla chiave PROVIDER_URL, grazie a ciò nel seguito della logica applicativa possiamo ottenere gli oggetti di tipo QueueConnectionFactory e Queue direttamente dall'albero JNDI caricato dall'istanza dell'application server (JBOSS) che gira su quella macchina.Dall'oggetto della classe QueueConnectionFactory così ottenuto otterremo un oggetto di tipo QueueConnection tramite il metodo createQueueConnection(); tramite il metodo createQueueSession(boolean transacted, int acknowledgeMode) otteniamo un oggetto QueueSession non transazionale perchè il boolean passato ha valore false, il che implica anche che la modalità di acknowledge (modalità con cui l'applicazione deve mandare un report sull'esito dell'invio del messaggio stesso al producer) indicata verrà ignorata; l'oggetto di tipo Queue, preso anch'esso da una chiamata a JNDI, ci serve per passarlo come unico argomento al metodo createSender(Queue queue) di QueueSession che ci ritorna appunto un QueueSender. In seguito viene creato un oggetto della classe TextMessage che implementa l'interfaccia Message come un'altra classe ObjectMessage che ci permette l'invio di messaggi che possono consistere di tutti gli oggetti java purché serializzabili naturalmente. Per il resto dopo l'invio del messaggio, nel blocco finally forziamo la chiusura della connessione precedentemente ottenuta. Sull'invio del messaggio tramite il metodo di QueueSender send(Queue queue, Message message) c'é da dire che se per qualsiasi motivo il server che ospita la coda è "giù", con questa implementazione, il client producer resta "appeso" per un certo tempo; per evitare questa eventualità si può implementare un invio del messaggio ad una coda locale su cui è in ascolto un mdb che nell'onMessage() fa da producer e rimanda il messaggio ricevuto alla coda remota, in questo modo però, il client è si slegato da una possibile attesa, ma non può neanche venire a conoscenza dell'esito dell'invio del messaggio alla coda remota.
Passiamo ora all’applicazione consumer che avrà una gerarchia come quella nella seguente figura:

Fig. 2 Un esempio di JMS con Jboss

Nella servlet dell'applicazione producer abbiamo visto come vengono presi gli oggetti QueueConnectionFactory e Queue, questi sono stati caricati nell'albero JNDI dall'istanza dell'application server su cui è deployata l'applicazione consumer al momento in cui viene fatto partire perché mappati in appositi files di configurazione; nel caso di JBOSS 3.0.x questi file sono jbossmq-destinations-service.xml e jbossmq-service.xml che si trovano entrambi nella directory <JBOSS_HOME>/server/default/deploy. Nel primo è mappata la stringa "queue/A" corrispondente ad una delle queue preconfigurate di JBOSS, nel secondo è mappata la stringa "ConnectionFactory"; per l'esempio presentato infatti non è stato necessario modificare tali file rispetto agli originali compresi nel "pacchetto" di JBOSS scaricato.
All'interno della directory mdb si trova l'unico componente dell'applicazione consumer, un semplice Message Driven Bean che non fa altro che prendere l'oggetto Message passatogli dal container nel metodo onMessage, castarlo al tipo aspettato (TextMessage) e ricavarne il testo contenuto e stamparlo su un file in locale tramite il metodo scriviTesto,
GenericMdBean.java:

package ejb.mdb;

import java.io.*;
import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;
import javax.ejb.EJBException;

public class GenericMDBean implements MessageDrivenBean, MessageListener {

    String filePath = new String();

     public void setMessageDrivenContext(MessageDrivenContext mdc) throws EJBException {}

     /**
     * al momento della creazione dell'mdb viene inizializzato il Context
     * e viene preso il path al file su cui scrivere presente come <env-entry>
     * nel file ejb-jar.xml.
     */
     public void ejbCreate() {
     try {
          InitialContext ctx = new InitialContext();
          filePath = (String)ctx.lookup("java:comp/env/destinazione");
          ctx.close();
          } catch(NamingException ne) {
            throw new RuntimeException(ne.toString());
      }
}

      /**
       * non fa nulla
       */
     public void ejbRemove() {}

     /**
      * viene preso il messaggio e castato al tipo aspettato, il testo preso da esso viene passato al metodo
      * scriviTesto(String testo)
      */
      public void onMessage(Message message) {
      if(message instanceof TextMessage) {
      TextMessage textMsg = (TextMessage)message;
      try {
           String testo = textMsg.getText();
           scriviTesto(testo);
           } catch(Exception exc) {
           throw new RuntimeException(exc.toString());
           }
       }
}

    /**
     * crea un OutputStream che punta al file in locale e vi scrive il testo del messaggio
     */
     public void scriviTesto(String testo) throws Exception {
        PrintWriter pw = new PrintWriter(new FileWriter(new File(filePath), true));
        BufferedWriter bw = new BufferedWriter(pw);
        bw.write(testo);
        bw.newLine();
        bw.flush();
        bw.close();
    }
}

Il Message Driven Bean GenericMdBean ha un normale descrittore di deployment ejb-jar.xml in cui specifico il tipo della destinazione - <destination-type> - in questo caso una Queue, il nome del bean e il path della classe del bean. Il tag opzionale <message-selector>, lasciato vuoto nell'esempio, può contenere una stringa di selezione utilizzata per filtrare i messaggi ricevuti. Per quanto riguarda il tag <transaction-type> questo può contenere i valori bean o container e sta ad indicare la demarcazione della transazione ma poiché il client non ha alcun modo per richiamare direttamente i bean a messaggi, il client non può propagare il suo contesto della transazione al bean a messaggi, allora la transazione che qui viene specificata è una transazione che può sì essere creata ma che riguarderà la chiamata al metodo onMessage e quello che accadrà dopo ma sempre nel contesto dell'applicazione consumer.

ejb-jar.xml:

<?xml version="1.0"?>
<!DOCTYPE ejb-jar>
     <ejb-jar>
       <enterprise-beans>
           <message-driven>
              <ejb-name>GenericMDBean</ejb-name>
              <ejb-class>ejb.mdb.GenericMDBean</ejb-class>
              <message-selector></message-selector>
              <transaction-type>Bean</transaction-type>
              <acknowledge-mode>Auto-acknowledge</acknowledge-mode>
              <message-driven-destination>
              <destination-type>javax.jms.Queue</destination-type>
              </message-driven-destination>
              <env-entry>
                 <description>Il path e il nome del file su cui scrivere</description>
                 <env-entry-name>destinazione</env-entry-name>
                 <env-entry-type>java.lang.String</env-entry-type>
                 <env-entry-value>C:\destinazione.txt</env-entry-value>
              </env-entry>
            </message-driven>
       </enterprise-beans>
</ejb-jar>

C'è poi bisogno di un descrittore di deployment "vendor specific", che per Jboss è jboss.xml, nel quale viene scelta la queue o il topic a cui l'mdb viene "dedicato" ovvero su cui l'mdb sta in ascolto. Il tag <ejb-name> deve contenere la stessa stringa riportata nel tag <ejb-name> dell'ejb-jar.xml per lo stesso mdb.
jboss.xml:

<?xml version="1.0"?>
    <jboss>
        <enterprise-beans>
           <message-driven>
               <ejb-name>GenericMDBean</ejb-name>
               <configuration-name>Standard Message Driven Bean</configuration-name>
               <destination-jndi-name>queue/A</destination-jndi-name>
           </message-driven>
          </enterprise-beans>
</jboss>

Per concludere, l'esempio è interamente scaricabile dal link Esempio_jms.zip che contiene le due applicazioni consumer e producer al cui interno si trovano sia i sorgenti che i compilati, all'interno delle dir Consumer e Producer sono presenti anche consumer.jar e producer.war che, se si possiede un disco C e si intende deployarli entrambi sulla stessa macchina, basta copiarli nella dir <JBOSS_HOME>/server/default/deploy e richiamare sul browser l'indirizzo "http://127.0.0.1:8080/producer", altrimenti si deve specificare in ejb-jar.xml il disco per il file su cui scrivere(Es.:D:\destinazione.txt) che se non esiste verrà creato dall'applicazione, se si vuole provare le due applicazioni su due macchine si dovrà mettere l'IP della macchina che ospita il consumer nel file web.xml al posto di "127.0.0.1" e ricreare il war e il jar in questo modo:
trovandosi nella directory Producer lanciare il comando :

C:\Producer>jar -cvf producer.war *

e dalla directory Consumer :

C:\Consumer>jar -cvf consumer.jar *

e rideployare i file ottenuti.
L'esempio gira con j2sdk1.4.1_01, j2sdkee1.4(o 1.3), jboss-3.0.4_tomcat-4.1.12(jboss3.0.x_tomcat4.0.x e superiori).
Un tutorial approfondito su jms è scaricabile o consultabile online all'indirizzo "http://java.sun.com/products/jms/tutorial/index.html".

 Attachments List
Generic DocumentEsempio_jms.zip
Generic Documentjboss.xml
Generic Documentindex.html
Generic DocumentProducerServlet.java



JavaPortal è ideato da:    
K-Tech Logo










LICENZA



Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto licenza Creative Commons

Sitemap  © 2002-2004 Copyright Information. Privacy . Today is sabato 19 giugno 2010