MDB(Message Driven Bean)(ENG)

MDB - Introduction on Message Driven Bean through an Example
G.Morreale

clicca qui per la versione italiana
Introduction:

MDB (Message Driven Bean) is a special type of bean that can perform certain acts in relation to the receipt of a JMS message.
It therefore does not respond to requests for a client but does the connection with "receiving a message."

Receiving a second domain chosen either on a topic or a tail.

The MDB is nothing but a JMS client operating currently received on a queue.

The Example

Suppose you want to create a MDB can process the web requests arriving on a given url(a servlet).
The MDB collect data on the object HttpServletRequest in order to keep a log file.

So any access to the MDB opens the file, and writes in the information: IP address of the client, user-agent, etc timestamp of the request.

But why have carried out such operations all'MDB? The JMS asynchronous system does not block the execution of the servlet pending operations (opening, writing files etc.) Log (useless to the user).

Initialize projects

In order to implement the project to create an application form with EAR WEB and EJB module.
The WEB module include the producer of messages EJB put the consumer: the MDB.

Elements from the example

As in any application will JMS 3 key elements:

  • provider of messages
  • client that produces messages
  • client that consumes messages

Comments about our 3 element list

  • provider messages - Queue to create the server resources in a position to collect the messages produced.
  • client that produces messages - Servlet (or rather a filter) that sends the message
  • client that consumes messages - MDB that processes the message by storing some data on file.

Configuring the JMS Provider 

I state that the provider, or rather its configuration, idepend on application servers used in this case it is used Glassfish V2.

Open the administration panel. For example, on localhost: 4848.
On Resources -> JMS Resources -> Connection Factories
You can create a new connection factory:


insert the JNDI name, or the value that allows you to find resource in the container, set as a kind of resource QueueConnectionFactory because we want to use in a specific queue and leave all other options.

note:
Glassfish creates a pool of connections to the queue so as to maximize the reuse of open connections to the factory in the same way that optimizes for example jdbc to a db.

Likewise, you can create a Destination:

Choosing as usual the JNDI name, the name of the physical destination and type of destination that, in this case is a queue.

Client that produces messages

The message will be produced in relation to access to a given url, we will use

http://localhost/mdb/test

So we must create and test a servlet filter to test the servlet, in the web of course (war).

note:
The filters intercept request so permits to edit request and the response of a given url pattern to forward the servlet or jsp or call or one or more other filters. They provide the ability to create units of code across multiple riusabile url (servlet or jsp) and create real chains filters (structuring development in a kind of plugins)

About the servlet build a trivial hello world servlet:

package servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author PeppeM
 */
public class test2 extends HttpServlet {
   
    /** 
    * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
    * @param request servlet request
    * @param response servlet response
    */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {            
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet test2</title>");  
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>ESEMPIO MDB - WEB TRACKING</h1>");
            out.println("</body>");
            out.println("</html>");            
        } finally { 
            out.close();
        }
    } 

    // <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
    */
    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
    */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

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

}

Prepare an object DTO (Data Transfer Object) 

Able to encapsulate the data that will serve the consumer client! 
This subject should be put in the EJB project and not in the WEB. 


import java.io.Serializable;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;


public class RequestDTO implements Serializable{
    String user_agent;
    private String remote_addr;
    private String query_string;
    private String method;
    private Date date;

    public String getMethod()
    {
        return method;
    }

    public void setMethod(String method)
    {
        this.method = method;
    }

    public String getQuery_string()
    {
        return query_string;
    }

    public void setQuery_string(String query_string)
    {
        this.query_string = query_string;
    }

    public String getRemote_addr()
    {
        return remote_addr;
    }

    public void setRemote_addr(String remote_addr)
    {
        this.remote_addr = remote_addr;
    }

    public String getUser_agent()
    {
        return user_agent;
    }

    public void setUser_agent(String user_agent)
    {
        this.user_agent = user_agent;
    }
   
    public RequestDTO(HttpServletRequest request)
    {
        user_agent = request.getHeader("user-agent");
        remote_addr = request.getRemoteAddr();
        query_string = request.getQueryString();
        method = request.getMethod();
        date = new Date();
    }

    @Override
    public String toString()
    {
        return date + " " + this.remote_addr + " " +  this.getMethod() + " " +  this.getUser_agent() + " " +  this.query_string + "\r\n";
    }
}


It is a simply object that extracts the necessary data and makes them available through the methods accessories. 
Note: The object must be Serializable! 

Also noteworthy is the override of the toString method to copy the value of all fields in a string DTO. 

The filter should instead look into the production of messages.

Create a new filter, using the wizard of NetBeans.
Still using the NetBeans wizard can automatically generate the code for the connection factory and the tail set up earlier.
In that case, click the right mouse button on the code of the filter and select

Enterprise Resources -> Send JMS Message

Setting the correct values in how to create resources referencing application server.

Or you can write by hand the following methods:


 private Message createJMSMessageFormyDestination(Session session, Object messageData) throws JMSException
    {
        ObjectMessage m = session.createObjectMessage((Serializable)messageData);                
        return m;
    }

    private void sendJMSMessageToMyDestination(Object messageData) throws NamingException, JMSException
    {
        Context c = new InitialContext();
        ConnectionFactory cf = (ConnectionFactory) c.lookup("java:comp/env/myQueueFactory");
        Connection conn = null;
        Session s = null;
        try
        {
            conn = cf.createConnection();
            s = conn.createSession(false, s.AUTO_ACKNOWLEDGE);
            Destination destination = (Destination) c.lookup("java:comp/env/myDestination");
            MessageProducer mp = s.createProducer(destination);
            mp.send(createJMSMessageFormyDestination(s, messageData));
        } finally
        {
            if (s != null)
            {
                s.close();
            }
            if (conn != null)
            {
                conn.close();
            }
        }
    }

automatically generated by NetBeans. 

To sum up the code to send the message you perform the following steps:

  1. Lookup resource ConnectionFactory
  2. creation of the connection
  3. creation of the session
  4. Lookup of destination
  5. Creation of client producer
  6. Sending message
  7. Closing session and connection

Then in the main method of filtering doFilter(ServletRequest request, ServletResponse response,                FilterChain chain)

you can call the method of sending the message

sendJMSMessageToMyDestination(new RequestDTO(request));

The content of the message will be the object of type HttpServletRequest request.
This object contains all the useful information log on to writing required on MDB.

Test production of messages 

Before you generate the client can consume messages produced by the filter, you should check if it works as a production, and launch the project and EAR call servlet test on which the filter is mapped. 

You may experience the following problem: 

javax.naming.NameNotFoundException: myQueueFactory not found
        at com.sun.enterprise.naming.TransientContext.doLookup(TransientContext.java:216)
        at com.sun.enterprise.naming.TransientContext.lookup(TransientContext.java:188)

This indicates that the value used in JNDI lookup phase is erroneous. 
In our case, for example NetBeans default considers the resources located in the path JNDI java: comp / env / resourcename, but when creating Glassfish included resources in the root, then you must make the following changes being lookup: 

ConnectionFactory cf = (ConnectionFactory) c.lookup("myQueue");

Destination destination = (Destination) c.lookup("myDestination");

Client that consumes messages - The MDB

Inside the EJB project, create an MDB. 

Once again you can use the wizard of NetBeans. 


Or hand-write the following code: 

package MDB;

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

@MessageDriven(mappedName = "myDestination", activationConfig =  {
        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
    })

public class myMDBBean implements MessageListener {

   
    public myMDBBean() {
    }

    public void onMessage(Message message) {
    }
   
}



Through the use of dependency injection with a few notes you can instantiate the client JMS (the MDB).

@MessageDriven(mappedName = "myDestination", activationConfig =  {

        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),

        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")

    })

  It indicates that the bean is MessageDriven and add a couple of properties to indicate the type of destination (queue or topic) and the destination itself (previously set by the system). 
  With only another step you end configuration dell'MDB it is necessary to implement the MessageListener 
public class myMDBBeanimplements MessageListener {
public void onMessage (Message msg) {
FileWriter fw = null;
try
{
ObjectMessage m = (ObjectMessage) message;//casting per estrarre la corretta tipologia di messaggio
RequestDTO requestDTO = (RequestDTO) m.getObject();//estrazione dei dati dal messaggio

//open. write and close the file
fw = new FileWriter("c:\\logmdb.txt", true); //look! the windows path.
fw.append(requestDTO.toString());

} catch (IOException ex)
{
Logger.getLogger(myMDBBean.class.getName()).log(Level.SEVERE, null, ex);
} catch (JMSException ex)
{
Logger.getLogger(myMDBBean.class.getName()).log(Level.SEVERE, null, ex);
} finally
{
try
{
fw.close();
} catch (IOException ex)
{
Logger.getLogger(myMDBBean.class.getName()).log(Level.SEVERE, null, ex);
}
}

}


  Conclusion 

The example is able to process data without losing time, "the servlet so that they write in a log file.
Indeed launching 'n' times the servlet will notice the inclusion of n lines in the log file.

Note: Possible Problems
If the file server.log Glassfish note of the following string

DirectConsumer: Caught Exception delivering messagecom.sun.messaging.jmq.io.Packet can not be cast to com.sun.messaging.jms.ra.DirectPacket

know that this is a know issue https://glassfish.dev.java.net / issues / show_bug.cgi? id = 3988
  With the technology JMS client even if the consumer is not active at the time was posted, the messages will be delivered later. 
To demonstrate this, it is possible
  • delete temporary the mdb project;
  • delete the log file "logmdb.txt"
  • build and redeploy the project on the serv
  • launch the servlet test (which will generate a message to every call).

  • remake the MDB
  • build and redeploy the project on the server

  WITHOUT launch servlet will notice that the logs were put on the line calls when the MDB 
was not completely present.



1 comment:

Giuseppe Morreale said...

Thank you for congratulations!