Capture the benefits of asynchronous logging

Develop an asynchronous log service using JMS and Hibernate

Developing a log service that suits common needs often provokes debate. Numerous homegrown log frameworks are available in addition to many professional and open source products. Products such as log4j and J2SE 1.4 logging are worth mentioning. Many frameworks were developed before the advent of JMS (Java Message Service), and thus, without the benefits of asynchronous messaging. Frameworks designed after JMS's initiation benefited from asynchronous messaging.

In a traditional synchronous log model, the caller cannot execute further unless the log service returns successfully. All calls are blocked until the record is persisted or acknowledged by the log service. That clearly results in an overhead, especially if an application is designed to code numerous log messages. Imagine a source file consisting of a large number (sometimes hundreds) of log statements. Each statement should be logged before the following statement is processed—clearly a time-consuming process. In a distributed-computing environment, obviously, clients operate concurrently. The alternative is to build a concurrent client architecture using the traditional log service, although performance may remain an issue. Distributed computing architectures may also have deployments spread across many servers (geographically similar or dissimilar locations), in which case, logging to a central database synchronously is cumbersome, if not impossible.

This article will help you develop a simple log service. The service creates some log messages, sends them across the network to a JMS provider, and persists them in the database. For this purpose, we use JMS for its asynchronous benefits and Hibernate for its simple yet powerful persistence features. You can persist data in many ways, such as through standalone JDBC (Java Database Connectivity) connections, EJB (Enterprise JavaBeans), and stored procedures. I vote for tools that create domain entities from plain-old Java objects. In this article, I move away from constructing the domain model using EJB entities by using Hibernate.

Before developing the log service, let's look at some important concepts vital for developing this application.

Enterprise messaging

J2EE continues to mature with respect to distributed computing environments. We can leverage many of its features, such as JMS, to develop asynchronous services. JMS is a specification from Sun Microsystems and its partners that covers inter-application messaging standards. See Resources to download the specification. Third-party vendors implement the specification defined by Sun. JMS is designed to handle different message types without the messages having knowledge of each other.

Remote procedure call (RPC) models such as RMI (remote method invocation), SOAP (Simple Object Access Protocol), and ORB (object request broker) are interface-centric, meaning the publisher expects a return value. In the case of JMS, the sender has no knowledge of the receiver. The applications that send and receive messages do not communicate via a standard communication channel. In fact they delegate the responsibility of delivering the messages to the JMS provider. The applications' jobs are completed when they post a message to the destination.

Messaging flavors

JMS provides two messaging flavors: point-to-point and publish/subscribe. Point-to-point is a messaging model based on the queuing concept. Each client sends messages to a destined queue, wherein the messages are placed on a stack. The messages are persisted and can survive a server crash (guaranteed messaging benefit). The JMS provider will deliver the messages pushed onto this queue to consumers. In the case of a point-to-point model, a single message is delivered to a single consumer.

The publish/subscribe model is a broadcast model in which all clients interested in a particular subject register themselves as listeners to that subject. A publisher publishes its message to the destination (topic). The JMS provider takes responsibility for distributing the messages to the interested parties (listeners). In this model, all interested parties consume each message.

Message-driven beans

EJB 2.0 introduced a new type of bean component called the message-driven bean (MDB). This bean is nonresponsive to client invocations. In fact, no interface is provided for the client to call on the MDB. Only the container can activate MDBs upon receiving the appropriate JMS message. They are nothing but message consumers. MDB implementation is neat. All the JMS administration information is described in the deployment descriptors, while the activity of receiving and processing the messages occurs literally in the onMessage() method. Please see Resources for further information on MDBs.

Hibernate

Hibernate is an open source product that eliminates JDBC coding. It is a tool for object-relational (OR) mapping within Java environments. It creates a mapping between Java objects and database entities. Object-relational mapping is a key requirement in enterprise projects. The data produced or changed as a result of processes must be persisted. Persisting data can turn into a nightmare for developers, especially if the data model is always changing the user requirements. Hibernate can alleviate complications resulting from data persistence. Refer to Resources for more information on Hibernate. The log service framework we develop in this article uses Hibernate as a medium for OR persistence.

The asynchronous log service

In the above sections, I briefly discussed the major ingredients vital for developing this log service. Now, let's develop a simple framework that utilizes the capabilities of JMS and Hibernate persistence.

Conceptually, the asynchronous log service works like this: The client creates a log message (value object) and asks a helper class, JMSLogger, to publish the message to the queue. JMSLogger creates a JMS ObjectMessage with a log message and posts it to the queue. The container invokes an MDB listening to this queue as soon as the message arrives by calling the MDB's callback method, onMessage(). The MDB reads the message and persists into the database using Hibernate.

The log service uses JMS's point-to-point messaging. Once a log message is sent, the client doesn't need to worry about where the message has been sent or whether it has even been sent. It relies on the underlying JMS messaging implementation for the reliable and guaranteed message delivery.

The figure below illustrates the log service's main components.

Functional diagram: Main components of log service

Framework components

Let's look at the components in detail before we delve into implementation specifics. The log client is this application's entry point. Minor differences arise when this client is used as a non-J2EE client (see examples in Resources). The log client uses JMSLogger to post the messages to the queue. It creates a log message with appropriate severity and instantiates JMSLogger to post the messages to the LogMessageQueue destination.

JMSLogger is a JMS-aware class that associates itself with the destination once it is instantiated. Hence, when created by the client, the JMSLogger looks up the connection factory and queue from the JNDI (Java Naming and Directory Interface) naming space and opens the session with the JMS provider. The class then starts sending the messages to the destination. Clients access the log service by simply creating an instance of the log client and formally calling the methods on it. If necessary, in a J2EE environment, this object can be instantiated only once during startup using Java Management Extensions (JMX) managed beans (MBeans) when the application server starts. Clients can then look for this service from JNDI and call the methods to persist the log messages as usual.

LogMessageQueue is the JMS destination wherein the log messages are posted. An MDB listens to this queue. As soon as a message arrives to the queue, the EJB container invokes the MDB and delegates the message for further processing. The MDB extracts the log messages, and, by using Hibernate's persistence mechanism, it persists the log message into the database.

The LogMessage is a Java value object that carries log information to be persisted. It is designed to hold minimal log information, such as short description, long description, log date, and severity.

Implementation

Now I'll describe the log service's implementation details. You can download the all implementation code from Resources and unpack it to a local directory.

Create the log table

First let's create a table that can store the log information. All we need is the log message's short description, long description, log level, and log date. The script listed in Listing 1 creates a table called LOG_DATA with those five fields. Except for the MESSAGE_ID, all the fields are self-explanatory. Use the script provided in the source code to create this table.

MESSAGE_ID is the primary key attribute assigned by the application. I'll discuss this field further in the "Hibernate Mapping File" section.

Listing 1: Create log table script

create table LOG_DATA
(
  MESSAGE_ID VARCHAR2(30) not null,
  SHORT_DESC VARCHAR2(20),
  LONG_DESC  VARCHAR2(200),
  LOG_DATE   DATE,
  LOG_LEVEL  VARCHAR2(10)
)

LogMessage

LogMessage is a value object to be persisted in the log database. Log clients create this object with proper log information.

As I mentioned earlier, we use Hibernate for persisting this LogMessage object.

Create a Java class that represents the above table. I have created the class with five attributes, each representing the table's columns. Also create the attributes' setters and getters, as Hibernate requires them.

Hibernate mapping file

To persist the LogMessage, Hibernate requires a mapping file. As the name suggests, the mapping file is an XML file that defines the object-relational mapping. In our case, it defines the attributes of the LogMessage class tied with table columns. The syntax of this XML-mapping file is quite straightforward:

Listing 2: Hibernate OR mapping file

<hibernate-mapping>
<!-Provide the name of the class and a mapping table here à
   <class name="com.madhusudhan.articles.logger.LogMessage"
table="LOG_DATA">
      <!-id represents the primary key. In our case, MESSAGE_ID is the
primary key.
      The key is generated by the application  à
         <id name="messageId" unsaved-value="id_value">
         <column name="MESSAGE_ID" sql-type="VARCHAR2(30)"
not-null="true"/>
          <generator class="assigned"/>
      </id>
      <!- map the Attributes of the class with the table columns à
      <property name="shortDesc">
         <column name="SHORT_DESC" sql-type="VARCHAR2(20)"
not-null="false"/>
      </property>
      <property name="longDesc">
         <column name="LONG_DESC" sql-type="VARCHAR2(200)"
not-null="false"/>
      </property>
      <property name="logDate">
         <column name="LOG_DATE" sql-type="DATE"  not-null="false"/>
      </property>
      <property name="logLevel">
         <column name="LOG_LEVEL" sql-type="VARCHAR2(10)"
not-null="false"/>
      </property>
   </class>
</hibernate-mapping>

I'll run through this file briefly:

The first line of the file (<class name="com.madhusudhan.articles.logger.LogMessage" table="LOG_DATA">) establishes mapping between our class and the database table. Provide the fully qualified name for the class attribute. The id attribute represents the primary key value. The attribute of the class messageId maps to key column MESSAGE_ID, which has a tag called generator. This class generates the primary keys for the table, according to your requests. In our example, I told Hibernate to assign the id to the application. You can also assign the id using generator classes supplied with the Hibernate distribution. Refer to Hibernate documentation for further details.

Remember to name this file LogMessage.hbm.xml

JMSLoggerBean (MDB)

The JMSLoggerBean listens to the messages posted to the queue and persists them to the database. The container invokes JMSLoggerBean when a message arrives. In the onMessage() method, the MDB asks Hibernate to persist the object. Because Hibernate simplifies the persistence process, I don't ask the MDB to pass the message to some other component, such as the session bean, for further processing. The MDB can do all the business processing itself.

The onMessage() method looks like this:

public void onMessage(javax.jms.Message message) {
   try {
   // Cast the JMS message to ObjectMessage type
   ObjectMessage objMsg = (ObjectMessage)message;
   // And extract the log message from it
   LogMessage logMsg = (LogMessage)objMsg.getObject();
   // Persist the message
   persistMessage(logMsg);
   }catch (Exception t) {
      t.printStackTrace();
   }
}

As soon as the message hits the queue, the MDB is invoked by the container and then calls the onMessage() method. In the method, retrieve the LogMessage from the JMS ObjectMessage and persist it by calling the following method:

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more