TP4 EJB : Utilisation de JMS et de Message Driven Beans

De $1

Version de 06:15, 23 Avr 2024

cette version.

Revenir à liste des archives.

Voir la version actuelle

 

Faisons marcher l'exemple du cours

Repartez du TP3. Dans la liste des EJBs, ajoutez un Message Driven Bean. Vous suivrez les étapes ci-dessous :

1 - Ajouter un message driven bean au projet EJB (celui avec le grain de café)

MDB1ajout.jpg

2) Configurer le bean. On lui donne un nom puis on indique le nom de la Queue de messages (ici "jms/logginMessages"). On indique aussi le type du gestionnaire de messages (Queue).

MDB2config.jpg

Le Bean est généré puis ajouté au projet. Le source doit ressembler à cela :

package tp2;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;

/**
 *
 * @author michel
 */
@MessageDriven(mappedName = "jms/loggingMessages", activationConfig =  {
        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
    })
public class MDBduCours implements MessageListener {
    
    public MDBduCours() {
    }

    public void onMessage(Message message) {
    }
    
}


Dans la méthode onMessage(), mettez le code de l'exemple du cours. Ce Bean doit ressembler à celui-ci une fois terminé: 

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tp2;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 *
 * @author michel
 */
@MessageDriven(mappedName = "jms/loggingMessages", activationConfig = {
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class MDBduCours implements MessageListener {

    public MDBduCours() {
    }

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            TextMessage tm = (TextMessage) message;
            try {
                String text = tm.getText();
                System.out.println("Received new message :" + text);
            } catch (JMSException e) {
            }
        }
    }
}

Notez que le nom de la destination est "jms/NewMessage", si vous allez voir dans la console d'administration du serveur d'application après avoir déployé le projet, vous trouverez sous jms/resources sa définition. C'est la même chose que pour les sources de données : on peut déployer de nouveaux Topic ou Queues de messages à la volée. Leur définition est ajoutée (dans le cas de Glassfish) dans le descripteur propriétaire sun-resources.xml :

MDB3sun-resources.jpg

Vous pouvez jeter un oeil à ce fichier, vous retrouverez la source de données du TP3 ainsi que la définition de la Queue de messages "jms/loggingMessages".

2 - Créez une servlet de test pour ce bean


Vous allez ajouter au projet web une servlet de test, appelez-la par exemple ServletTestMDB. Cette servlet enverra lors de son invocation un message texte à l'EJB précédente. 

Snap5.jpg

Snap6.jpg

Ouvrez le source de la Servlet. Vous retrouvez une Servlet vide classique. On va utiliser un wizard pour insérer du code afin que cette Servlet soit capable d'envoyer un message à la Queue de messages jms/loggingMessages. Rappel : la Servlet ne sait pas que le Message Driven Bean existe, elle se contente juste d'envoyer un message indiquant qu'elle souhaite un traitement.

Avec netbeans, faites dans le source de la servlet, curseur dans la méthode ProcessRequest, bouton de droite / insert code / send JMS Message :

Snap8.jpg

Cela va faire apparaitre un wizard dans lequel vous allez indiquer le nom de la Queue de message dans laquelle vous allez publier :

Snap9.jpg

 

Cela va générer du code. Etudiez-le ! Essayez de comprendre ce qu'il fait.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package tp2;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author michel
 */
public class ServletTestMDB extends HttpServlet {
    @Resource(name = "jms/loggingMessages")
    private Queue loggingMessages;
    @Resource(name = "jms/loggingMessagesFactory")
    private ConnectionFactory loggingMessagesFactory;
   
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        
    } 

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    } 

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Returns a short description of the servlet.
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>

    private Message createJMSMessageForjmsLoggingMessages(Session session, Object messageData) throws JMSException {
        // TODO create and populate message to send
        TextMessage tm = session.createTextMessage();
        tm.setText(messageData.toString());
        return tm;
    }

    private void sendJMSMessageToLoggingMessages(Object messageData) throws JMSException {
        Connection connection = null;
        Session session = null;
        try {
            connection = loggingMessagesFactory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer messageProducer = session.createProducer(loggingMessages);
            messageProducer.send(createJMSMessageForjmsLoggingMessages(session, messageData));
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot close session", e);
                }
            }
            if (connection != null) {
                connection.close();
            }
        }
    }

}

En particulier, notez que les lignes :

@Resource(name = "jms/loggingMessages")
    private Queue loggingMessages;
    @Resource(name = "jms/loggingMessagesFactory")
    private ConnectionFactory loggingMessagesFactory;

Correspondent aux 4 premières étapes indiquées en cours, que l'on pourrait écrire (en utilisant explicitement JMS) comme ceci :

// 1 Récupération de la factory JMS 
InitialContext ctx = new InitialContext();  
 opicConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("jms/loggingMessagesFactory");  
	
// 2 Use connection factory to create a JMS connection 
QueueConnection connection = factory.createQueueConnection();   

//3 Use connection to create a session 
QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);   

// 4 Lookup destination    
Queue queue = (Queue) ctx.lookup("jms/loggingMessages");

Evidemment, avec les annotations de code c'est plus simple ! On ne codera pas les lignes ci-dessus, et tant mieux !

Des méthodes utilitaires sont également générées, en particulier la méthode sendJMSMessageToNewMessage qui prend un objet en paramètre (messageData). Regardez ce qu'elle fait... Elle nous mache le travail. Sauf que par défaut elle suppose que le message est un textMessage. Pour la question suivante du TP, vous utilisez un ObjectMessage, il faudra donc légèrement modifier la ligne de création du message.

private Message createJMSMessageForjmsLoggingMessages(Session session, Object messageData) throws JMSException {
        // TODO create and populate message to send
        TextMessage tm = session.createTextMessage();
        tm.setText(messageData.toString());
        return tm;
    }

    private void sendJMSMessageToLoggingMessages(Object messageData) throws JMSException {
        Connection connection = null;
        Session session = null;
        try {
            connection = loggingMessagesFactory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer messageProducer = session.createProducer(loggingMessages);
            messageProducer.send(createJMSMessageForjmsLoggingMessages(session, messageData));
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot close session", e);
                }
            }
            if (connection != null) {
                connection.close();
            }
        }
    }

 

Regardez également la méthode processRequest. Appelons depuis cette méthode la méthode qui envoie un message JMS. 

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        try {
            sendJMSMessageToLoggingMessages("Ceci est un message texte envoyé au MDB");
        } catch (JMSException ex) {
            Logger.getLogger(ServletTestMDB.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

Elle est finalement très concise, pour envoyer un message texte on se contente d'une ligne de code !

Compilez, déployez, testez la servlet de test et vérifiez dans la console du serveur que le message est bien arrivé :

Snap10.jpg

Si vous faites "reload" plusieurs fois dans votre navigateur, vous verrez qu'à chaque fois un message arrive !

Modifiez maintenant le code du Message Driven Bean pour simuler un traitement long. Avec l'exemple ci-dessous nous simulons un délai de 10 secondes entre chaque traitement de messages :

public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            TextMessage tm = (TextMessage) message;
            try {
                String text = tm.getText();
                System.out.println("Received new message :" + text);
                System.out.print("J'attend dix secondes que le traitement soit terminé");
                Thread.sleep(10000); // 10 fois 1000 millisecondes
            } catch (InterruptedException ex) {
                Logger.getLogger(MDBduCours.class.getName()).log(Level.SEVERE, null, ex);
            } catch (JMSException e) {
            }
        }
    }

Relancez le projet, et regardez la console pendant que vous faites "reload" plusieurs fois d'affilée. Le client (le navigateur, la servlet) n'est pas bloqué et reprend la main instantanément, alors que les messages reçus apparaissent dans la console toutes les 10 secondes ! En revanche dans la console on a les messages dans un ordre bizarre. Quelle est l'explication ? Cf le cours et réflechissez un peu !

La non-attente du client fait tout l'intérêt de JMS. Imaginez une vidéo à encoder, un ordre de paiement qui nécessite de "parler" à la banque, etc...

3 - Une vraie application : on va envoyer des messages de demande de paiement électronique

Imaginez un site de e-commerce. Des clients remplissent leur panier virtuel et passent une commande. Le site envoie un message JMS à la banque demandant un transfert bancaire du compte en banque du client vers celui du site de e-commerce. On va réaliser cette opération. Cette fois-ci le message envoyé ne sera pas un message de type TextMessage mais de type ObjectMessage. Voir le cours sur les différents types de messages JMS.

Première étape : créer une classe OrdreDeTransfert.java qui contiendra trois attributs : l'id du compte source, l'id du compte de destination et le montant à transférer.

Travail à faire :

  1. Backupez par sécurité votre TP3 quelque part. On ne sait jamais.
  2. Ecrire la classe OrdreDeTransfert
  3. Créer un message driven bean MBDTransfertBancaire, qui écoute sur la Queue de messages jms/OrdresTransfertBancaires
  4. Ce MBD accepte comme message dans sa méthode onMessage() un ObjectMessage, que vous casterez en OdreDeTransfert.
  5. Vous récupèrerez les informations intéressantes à l'intérieur de cet objet, puis sous-traiterez le transfert au session bean GestionnaireDeCompteBancaire que vous avez écrit pour le TP No 3. Le MBD est un client de l'EJB (pas de new !!!! Utilisez @EJB !!!). Utilisez la méthode de transfert existante, que vous avez déjà écrite dans le TP3.
  6. créez une nouvelle Servlet de test ServletOrdreDeTransfert qui instancie un OrdreDeTransfert et l'envoie par JMS à la Queue de messages jms/OrdresTransfertBancaires sous la forme d'un objet. Attention à bien transformer les TextMessages en ObjectMessages et les setText en setObject.
  7. Testez l'envoi de l'ordre, vérifiez que les données dans vos tables sont bien mises à jour (cela doit générer des OperationsBancaires)
  8. Maitenant interfacez la JSP qui propose les menus de votre TP3 pour passer par cette servlet en cas de transfert, au lieu de la servlet existante. Ou bien modifiez la servlet existante.