An Example


An Example

Let us consider an example in which an application system wishes to log entries in a stockroom. Since the logging data will be used later for auditing purposes, they should be maintained securely in a database. In order not to prolong a booking unnecessarily, a decision is made to carry out the logging asynchronously. Since the data must not be lost, the Java message service is used. With its support for distributed transactions and persistent queues and topics, it offers sufficient data security for this purpose. The message-driven bean LoggerBean is responsible for placing incoming data securely in a database table (see Listing 6-12).

Listing 6-12: Example of a message-driven bean.

start example
package ejb.logger;
import javax.ejb.*;
import javax.jms.MessageListener;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.jms.JMSException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Date;
public class LoggerBean
implements MessageDrivenBean, MessageListener {
private static final String DS =
        "java:/comp/env/ds-name";
private static final String ST =
        "insert into logs(tst, msg) values(?, ?)";
private MessageDrivenContext mCtx       = null;
private transient DataSource dataSource = null;
public LoggerBean() {}
public void setMessageDrivenContext
                  (MessageDrivenContext ctx)
{
    mCtx = ctx;
}
public void ejbCreate() { }
public void ejbRemove() {
    dataSource = null;
}
public void onMessage(Message message) {
    try {
        initDataSource();
    } catch(NamingException nex) {
        //error reporting
        mCtx.setRollbackOnly();
        return;
    }
    String msg = null;
    try {
        msg = ((TextMessage)message).getText();
    } catch(ClassCastException ccex) {
        //error reporting
        mCtx.setRollbackOnly();
        return;
    } catch(JMSException jmsex) {
        //error reporting
        mCtx.setRollbackOnly();
        return;
    }
    try {
        logMessage(msg);
    } catch(SQLException sqlex) {
        //error reporting
        mCtx.setRollbackOnly();
        return;
    }
    }
    private void logMessage(String msg)
        throws SQLException
    {
        Connection        con = dataSource.getConnection();
        PreparedStatement pst = null;
        String            mm  = (msg == null) ? "" : msg;
        try {
            pst = con.prepareStatement(ST);
            Date dd = new Date(System.currentTimeMillis());
            pst.setDate(1, dd);
            pst.setString(2, mm);
            pst.execute();
        } finally {
            try { pst.close(); } catch(Exception ex) {}
            try { con.close(); } catch(Exception ex) {}
        }
    }
    private void initDataSource()
        throws NamingException
    {
        if(dataSource != null) {
            return;
        }
        Context ctx = new InitialContext();
        String dsname = (String)ctx.lookup(DS);
        dataSource = (DataSource)ctx.lookup(dsname);
    }
}
 
end example

By way of its environment the LoggerBean obtains for itself the name of the data source that it uses for database access. Via JNDI it obtains access to this data source. When a message arrives the EJB container calls the onMessage method and sends the message to the Enterprise Bean. The LoggerBean awaits a message of type javax.jms.TextMessage. It takes the text message and stores it together with the date and time of receipt in a database table with the name logs. If an error occurs that the LoggerBean cannot overcome, it uses the method setRollbackOnly of the message-driven context. It thereby notifies the EJB container that the processing has gone awry. The LoggerBean uses the transactions managed by the EJB container (see the example deployment descriptor in Listing 6-13). If the transaction is not committed, then the unsuccessfully processed message is resent to the message service.

Listing 6-13: Deployment descriptor of a message-driven bean.

start example
<?xml version="1.0" ?>
<ejb-jar version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
 <enterprise-beans>
   <message-driven>
     <!-- name of the Enterprise Bean -->
     <ejb-name>loggerBean</ejb-name>
     <!-- class of the Enterprise Bean -->
     <ejb-class>ejb.logger.LoggerBean</ejb-class>
     <!-- tells who should run the transactions -->
     <transaction-type>Container</transaction-type>
     <!-- tells for what queue or topic the
      Enterprise Bean is registered as recipient -->
     <message-driven-destination>
       <jms-destination-type>javax.jms.Queue
       </jms-destination-type>
     </message-driven-destination>
     <!-- environment entries -->
     <env-entry>
       <env-entry-name>ds-name</env-entry-name>
       <env-entry-type>java.lang.String</env-entry-type>
       <env-entry-value>wombatDS</env-entry-value>
     </env-entry>
     <!-- tells with what authorizations the,
      Enterprise Bean is equipped. -->
     <security-identity>
       <run-as-specified-identity>
         <role-name>guest</role-name>
       </run-as-specified-identity>
     </security-identity>
   </message-driven>
  </enterprise-beans>
</ejb-jar>

 
end example

Listing 6-13 shows the deployment descriptor associated with the LoggerBean. For a complete description of the elements of the deployment descriptor for a message-driven bean, see [21].

In order for an event to be logged, the message must be sent via the message service. For this each component of the system that carries out a booking can use an auxiliary class, as shown in Listing 6-14.

Listing 6-14: Example of a client of a message-driven bean.

start example
package ejb.logger;
import javax.jms.JMSException;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueConnection;
import javax.jms.QueueSession;
import javax.jms.QueueSender;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.naming.NamingException;
import ejb.util.*;
public class Logger {
    public static final String FACTORY =
        "java:/env/jms/DefaultConnectionFactory";
    public static final String QUEUE_NAME =
        "java:/env/jms/LoggerQueue";

    private static QueueConnectionFactory queueCF  = null;
    private static Queue                  theQueue = null;
    static {
        try {
            queueCF = (QueueConnectionFactory)
                         Lookup.get(FACTORY);
            theQueue = (Queue)
                         Lookup.get(QUEUE_NAME);
        } catch(NamingException nex) {
            nex.printStackTrace();
            throw new
                IllegalStateException(nex.getMessage());
        }
    }
    public static void log(String logMessage)
        throws NamingException, JMSException
    {
        QueueConnection qc    = null;
        QueueSession    qs    = null;
        QueueSender     qsend = null;
        try {
            qc = queueCF.createQueueConnection();
            qs = qc.createQueueSession
                        (false, Session.AUTO_ACKNOWLEDGE);
            qsend = qs.createSender(theQueue);
            TextMessage tm = qs.createTextMessage();
            tm.setText(logMessage);
            qsend.send(tm);
        } finally {
            try { qsend.close(); } catch(Exception ex) {}
            try { qs.close(); }    catch(Exception ex) {}
            try { qc.close(); }    catch(Exception ex) {}
        }
    }
}
 
end example

This example uses an auxiliary class called Lookup, which has the method get. Access to the naming service is contained within this auxiliary class and the method get in order to limit the example code to its essentials. The implementation of this auxiliary class is part of the example code for this book.

With the static method log a text message can be sent via the LoggerQueue. This message is received by an instance of the LoggerBean and stored in the database. The "client code" shown in Listing 6-14 for the use of a message-driven bean is no different from the code of a normal JMS client. The code shown could be optimized in two different ways. If messages are sent from only a single thread, then the JMS connections and the JMS session could be held open, to avoid continual opening and closing. If the class Logger is used in an application in which multiple threads send messages, then a session pool could be used, as described in the section on sending a message.