JMS Messaging Using GlassFish

Communication between distributed heterogeneous systems has become an inevitable requirement for today's e-business. The advent of messaging standards like Java Messaging Service (JMS) has now made it easy to develop loosely coupled, distributed systems communicating synchronously or asynchronously to exchange business data and events.

This article outlines details of messaging in general and enterprise messaging in particular using JMS and Message-Driven Beans (MDB). The messaging capabilities of the GlassFish application server as well as its configuration setup are covered in this article. In order to better explain these technologies, a simple yet realtime use case and its implementation details are also discussed here.

Messaging

In simple terms, messaging is the communication between two parties. Enterprise messaging can be defined as the communication between two software components or even two applications. Messaging is as simple as sending a physical mail, where the mail sender prepares the message, gives the correct postal address, and selects the right postal service. In enterprise messaging, also, there will be a message sender who will send messages in specific format to a destination. The destination will be message-oriented middleware or a message queue that helps to exchange messages. Also, there will be message consumers that receive messages from the destination, either synchronously or asynchronously. It is not mandatory that the sender and the receiver be available at the same time; nor do they need to know each other to exchange messages.

One of the key benefits of a messaging system is that it keeps distributed systems loosely coupled. Loose coupling makes messaging solutions different from other tightly coupled communication solutions like Remote Method Invocation (RMI), CORBA, etc.

JMS Messaging

Java Messaging Service is a messaging API based on the Java 2 Platform, Enterprise Edition (Java EE). It defines a set of common interfaces for creating, sending and receiving messages.

JMS supports two messaging models:

  • Point-to-point (PTP)
  • Publish-subscribe

The point-to-point messaging model relies on the concept of message queues, wherein messages will be addressed to a specific destination called a queue. A receiver consumes a message from this queue, processes it, and acknowledges its receipt. Key features of the point-to-point messaging model are:

  • One consumer per message.
  • No timing dependency between sender and receiver.

In the publish-subscribe model, messages will be addressed to a destination called a topic. Here, the message producer publishes messages, and consumers subscribe to messages. The key features of this messaging model are:

  • Multiple consumers per message.
  • A timing dependency between sender and receiver; i.e., the consumer must be active to receive messages. However, the JMS API allows subscribers to create durable subscriptions to receive messages even if subscriber is inactive. With a durable subscription, the JMS provider retains the subscription's messages until they are received by the subscription or until the message expires.
Message Types

JMS supports five message types:

  • Text: A simple text message or a java.lang.String object.
  • Object: A Serializable Java object.
  • Bytes: A simple stream of bytes.
  • Map: A set of name-value pairs.
  • Stream: A stream of primitive values.

Message-Driven Beans

A message-driven bean is an enterprise that which helps to processes messages asynchronously. An MDB acts as a listener for JMS messages. JMS clients can't locate MDBs and invoke methods directly; instead the client sends messages to the destination to which the MDB is listening. The EJB container calls an MDB's onMessage method upon receiving a message. This method normally casts the message to one of the five JMS message types and handles it as per the application's business logic. MDBs work in asynchronous mode and are stateless and transaction-aware. These features make MDBs highly scalable and offer a robust solution for enterprise messaging.

JMS Support in GlassFish

GlassFish is an open source application server for developing and deploying Java EE applications and web services. This server is compliant with Java Enterprise Edition 5 (Java EE 5), and is in fact the reference implementation of Java EE 5.

The GlassFish server offers tremendous support for JMS messaging, by offering a fully integrated JMS provider. The Java Message Service API is implemented by integrating Sun Java System Message Queue software into GlassFish, providing transparent JMS messaging support.

GlassFish supports two JMS resources: the connection factory and destination resources.

A connection factory is an object used by JMS clients to create a connection to a JMS provider. Connection factories can be of three types:

  • Connection factory: Used by point-to-point as well as publish-subscribe messaging models.
  • Queue connection factory: Used by the point-to-point messaging model.
  • Topic connection factory: Used by the publish-subscribe messaging model.

A destination is the object used by JMS message producers to post the message to and the source from which JMS message consumers consume the message. Supported destination types are:

  • Queue: The queue is the destination for point-to-point communication.
  • Topic: The topic is the destination for publish-subscribe communication.

Following are some of the JMS connection features supported by GlassFish.

Connection Pool

A GlassFish server automatically pools JMS connections. The user can set connection pool properties using the GlassFish admin console or asadmin commands. Connection pool details are configured while creating connection factories. Some of the connection pool parameters supported by GlassFish are:

  1. Initial and minimum pool size: Indicates the number of initial connections in the pool. This is also the minimum number of connections set for the pool.

  2. Maximum Pool Size: The maximum number of connections available in the pool.

  3. Pool resize quantity: The number of connections to be removed when the pool reaches idle timeout.

  4. Idle timeout: The maximum time that a connection may remain idle in the pool.

  5. Max wait time: The maximum wait time before a connection timeout is sent.

  6. Failure action: In case of a failure, the connection can be closed and reconnected.

  7. Transaction support: The level of transaction support. Supported transaction types are "local transaction," "XA transaction," and "no transaction."

  8. Connection validation: If this property is selected, connections will be validated before passing them on to the application.

Connection Failover

This feature enables the application server to reconnect to the message broker if the connection is lost. If reconnection is enabled and if the primary message broker goes down, then the application server will try to reconnect to another available broker. The user can configure the number of retries and the time interval between retries.

Accessing JMS Resources from Application

In GlassFish, the connection factory and destination can be accessed in two ways: by using Java Naming and Directory Interface (JNDI) lookup or using annotations.

JNDI Lookup

JMS clients can use the JNDI API to look up connection factories and message destinations.


InitialContext jndi = new InitialContext();
// Lookup queue connection factory
QueueConnectionFactory qFactory = (QueueConnectionFactory)jndi.
                lookup("webTrackerConnFactory");
// Lookup queue
Queue queue = (Queue)jndi.lookup("webTrackerQueue");

Annotations

Annotations are a declarative style of programming introduced in Java SE 5.0. Annotations are like metatags that can be applied to classes, constructors, methods, variables, etc.

The annotation @Resource is used for looking up connection factories and destinations. In a web application, if this annotation is placed on a variable, then the servlet container will inject the requested resource; i.e., the annotated variable will be pre-populated with an appropriate value before serving the request.


@Resource(name="connFactory", mappedName="webTrackerConnFactory")
private QueueConnectionFactory qFactory;

@Resource(name="jmsQueue", mappedName="webTrackerQueue")
private Queue queue;

Put Messaging to Work

So far we have discussed how JMS and MDB work together to facilitate asynchronous messaging. We have also seen GlassFish's capabilities and the JMS support it provides. Now we will see how we can make these technologies work with the help of a realtime use case called "Web Access Tracker."

Web Access Tracker helps site administrators or business users to monitor user request statistics like the number of visitors on a day, frequently accessed pages, page/service requested time, request serving time, etc. In the following section, I will explain a solution to collect the web access information for each page accessed by the user, without compromising on the performance of the actual application. You can download the full source code of this example, as well as its deployable binary file, from the link in the Resources section.

Solution

A JMS-based messaging solution has been selected to capture and process web access data with the help of other technologies like Java Architecture for XML Binding (JAXB), Java Server Faces (JSF), servlet filters, etc. GlassFish provides support for JMS messaging and also for deploying the application.

The demo application uses a servlet filter to intercept user requests and to extract required information from the request header. This information will be then posted to a message queue as a JMS message. Messaging is done asynchronously through a message-driven bean, which is the consumer for JMS messages. The data collected from the message will be persisted to an XML data store. JAXB is the technology of choice for accessing and storing data into XML files. User interfaces are developed using JSF.

System Requirements
  1. Java EE 5
  2. GlassFish Application Server
  3. Other library files: commons-beanutils.jar, commons-collections.jar, and commons-logging.jar from Apache

Servlet Filter

A servlet filter performs filtering tasks on either the request to a resource, the response from a resource, or both. You can write your own filter by implementing the Filter interface. The main method in this interface is the doFilter method, which performs the core filtering action. You should also implement the init and destroy methods in your filter class. These methods are invoked by the servlet container to initialize and destroy filters.

The sample application uses a servlet filter named WebTrackerFilter, which captures information pertaining to the request; say, the URL of requested page, host IP, requested time, etc. The information collected from the request will be sent as a Data Transfer Object (DTO) to the message dispatcher for further processing.


public void doFilter(ServletRequest request, 
        ServletResponse response, FilterChain chain)
        throws java.io.IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) 
                            request;
    HttpServletResponse httpServletResponse = (HttpServletResponse) 
                            response;
    

    // Extract required header parameters and set it in DTO
    RequestData data = new RequestData();        
    data.setQueryString(request.getQueryString());
    data.setRemoteAddress(request.getRemoteAddr());
    data.setRequestURI(request.getRequestURI());
    data.setResponseTime(elapsedTime);
    data.setUserAgent(request.getHeader("User-Agent"));
    // send the data to message dispatcher
    messagedispatcher.sendRequestData(data);
    chain.doFilter(httpServletRequest, httpServletResponse);    
}

Configuring Servlet Filter

We have seen how to program the custom filter, so now we need to configure the filter class in your web application deployment descriptor; i.e., the web.xml file. The filter class can be configured by adding the <filter> element in the deployment descriptor file. This element creates the name of the filter as well as the name of filter implementation class. The servlet/URL patterns can be mapped to the filter using the <filter-mapping> element. This causes the WebTrackerFilter to be called for all requests whose URL pattern is *.html. The following elements show how to configure WebTrackerFilter.


<filter>
    <filter-name>WebTrackerFilter</filter-name>
    <filter-class>demo.webtracker.filter.WebTrackerFilter</filter-class>
</filter>
<filter-mapping>
      <filter-name>WebTrackerFilter</filter-name>
      <url-pattern>*.html</url-pattern>
</filter-mapping>

Asynchronous Messaging

The request information is now available with the WebTrackerFilter; the next step is to develop a JMS client to post this data to the message queue as a JMS message. A point-to-point model has been selected for messaging.

The code snippet for developing a typical JMS client is shown below. You need to perform the JNDI lookup of connection factory and destination using the InitialContext, which retrieves the JMS-administered objects from the JNDI tree. Alternatively, you can use annotations to inject JMS resources during runtime. The webTrackerConnFactory and webTrackerQueue used in the code are the JNDI names of the connection factory and destination, respectively. The client assumes that these resources are already configured in GlassFish, hence the configuration needs to be done prior to the execution of this code. Refer to the section "Configuring JMS Resources in GlassFish" to see how to configure JMS resources in GlassFish.

A Connection to the JMS service provider can be created using the createQueueConnection method on the connection factory. The connection provides access to the underlying message transport.

The next task is to create a JMS Session using the createSession method on the Connection object. A session is a single threaded context for producing and consuming messages. The first argument to createSession decides whether the JMS session is transacted or not and the second argument indicates the message acknowledgment mode. In this sample, the session is not transacted and it automatically acknowledges on receiving messages.

Also, you need to create a MessageProducer for sending messages to the destination. The final step is to create the JMS ObjectMessage and set the request data as the message. This message is then dispatched to the message queue.


// Get queue connection factory
QueueConnectionFactory qFactory = (QueueConnectionFactory)jndi.
                lookup("webTrackerConnFactory");

// Get queue
Queue queue =  (Queue)jndi.lookup("webTrackerQueue");

// Get queue connection
QueueConnection qConn = (QueueConnection)qFactory.
                createConnection();

// Get session
Session session = qConn.createSession(false, 
            Session.AUTO_ACKNOWLEDGE);

// Set the JMS message
ObjectMessage msg = session.createObjectMessage();
msg.setObject(data);

// Send JMS message
session.createProducer(jmsLoc.getMessageQueue()).send(msg);

Message-Driven Beans

A message-driven bean will be the listener for JMS messages. When the messages reach the queue, the container calls the MDB's onMessage() method. This method casts the message to the original message type (i.e., ObjectMessage), which will be then formatted and persisted to the data store. The MDB implements the MessageDrivenBean interface and optionally, the MessageListener interface for the message type it supports; i.e., if the bean supports JMS, the interface to be implemented will be javax.jms.MessageListener.

Here's the code that receives a JMS message:


public void onMessage(Message message) {        
    if (message instanceof ObjectMessage) {
       RequestData data = (RequestData)((ObjectMessage)message).
                           getObject();
       // persist data
    }
}

If you are using Java EE 5, the MDB can be annotated with the @MessageDriven annotation. This annotation contains a mappedName element that specifies the JNDI name of the message queue to which the bean will listen. The MDB can use a @Resource annotation to inject a MessageDrivenContext resource. In this case, you don't have to declare the bean details in the EJB deployment descriptor. The first few lines of a MDB are shown below:


@MessageDriven(mappedName = "webTrackerQueue")
public class WebTrackerEJB implements MessageListener {

/** Context for the MDB. */
@Resource
private MessageDrivenContext mdbContext;
.........

Java Architecture for XML Binding (JAXB)

The data captured from the user request can be persisted to the data store. In this sample, an XML data store has been selected because of its simplicity. JAXB is the technology of choice for unmarshalling and marshalling the XML document. JAXB provides a convenient way to access and process XML content using Java objects by binding XML schema to a Java representation. The Java object representation of XML can be created using the following steps:

  1. Prepare the XML structure of data store.
  2. Create the XML Schema Definition (XSD). Refer to the webAccess-sample.xml and webAccess.xsd files provided in the webtracker-src/config directory to find more details about the XML data store and schema definitions used in this sample.
  3. Use JAXB's XJC command to generate the required Java files for the supplied XSD. The XJC.bat file is included in the <GlassFish_Root>/bin directory.
Unmarshal XML Document

Unmarshalling is the process of creating an object tree from an XML document. To unmarshal an XML document, you need to create a JAXBContext. The JAXBContext provides an abstraction for managing the XML/Java binding information. The Java package containing the schema-derived classes should be passed as an argument to create JAXBContext. The XML unmarshaller created using JAXBContext can be used for retrieving the root element of the XML document.

Here's the code for unmarshalling the XML data file:


 // create a JAXBContext
 JAXBContext jc = JAXBContext.newInstance
            ("demo.webtracker.xmlgen");

 //create UnMarshaller
Unmarshaller  unmarshaller = jc.createUnmarshaller();
JAXBElement rootElement = (JAXBElement)unmarshaller.unmarshal
            (new FileInputStream(xmlDataStorePath));

// Get the root element
WebAccess access = (WebAccess)rootElement.getValue();

Marshal XML Document

Marshalling creates an XML document from the content tree. To marshal an XML document, you need to create a JAXBContext.

Here's the code snippet for marshalling the XML data file:


// create a JAXBContext
 JAXBContext jc = JAXBContext.newInstance
            ("demo.webtracker.xmlgen");

//create a Marshaller and marshal to webAccessLog.xml
Marshaller  marshaller = jc.createMarshaller();

// Converts java object to XML data
marshaller.marshal(accessData, new File(xmlDataStorePath));

Configuring JMS Resources in GlassFish

If a JMS client wants to send messages to a destination, the resources like connection factory and destination need to be configured in GlassFish. These resources can be created either through the admin console of GlassFish or through the asadmin commands.

Create JMS Connection Factory

JMS connection factories allow an application to create other JMS objects programmatically. Use the following steps to configure a connection factory using the admin console:

  1. Log on to the GlassFish admin console.
  2. Expand the Resources -> JMSResources menu from the left-hand navigation bar.
  3. Select the Connection Factories node.
  4. Click the New button to create a new connection factory.
  5. Enter the following details:
    • JNDI Name as "webTrackerConnFactory". This is the unique JNDI name of the connection factory. JMS clients use this name to look up the connection factory. A JNDI name can be up to 255 characters, and must contain only alphanumeric, underscore, dash, or dot characters.
    • Resource Type as "javax.jms.QueueConnectionFactory". In the sample application, we have selected a point-to-point messaging model, so the resource type will be QueueConnectionFactory.
    • Description as "Webtracker Connection Factory".
  6. Click OK.
Create JMS Message Queue

The JMS destination serves as the repositories for messages. Use the following steps to configure the message queue using the admin console:

  1. Log on to the GlassFish admin console.
  2. Expand the Resources -> JMSResources menu from the left-hand navigation bar.
  3. Select Destination Resources.
  4. Click the New button to create a destination.
  5. Enter the following details:
    • JNDI Name as "webTrackerQueue". This is the unique JNDI name of the destination. JMS clients use this name to look up the message queue.
    • Physical destination name as "webTrackerQueue". This is the destination name in the message broker.
    • Resource Type as "javax.jms.Queue". The resource type will be Queue, as we are using a point-to-point messaging model.
    • Description as "Webtracker Queue Destination".
  6. Click OK.

Deploying Application in GlassFish

The application is packaged as an Enterprise Application Archive (EAR) file, which consists of a web module and an EJB module. The web module deals with the user interfaces of the application. The message-driven bean and other data access classes are packaged as the EJB module. The following steps explain the deployment of the sample application in GlassFish.

  1. Log on to GlassFish admin console.
  2. Select Applications -> Enterprise Applications from the left-hand navigation bar.
  3. Click the Deploy button to deploy the webtracker application.
  4. Browse the webTracker.ear file and upload it.
  5. Click OK.
  6. Copy the webAccess.xml file provided in webtracker-src/config to any local directory. Add a system property called xmlstore.path and point it to this directory. You can set the system property through the GlassFish admin console by selecting Application Server -> JVM Settings -> JVM Options -> Add JVM Option. A sample entry looks like -Dxmlstore.path=C:/temp/webAccessLog.xml.

Deployment Descriptors

ejb-jar.xml

The ejb-jar.xml deployment descriptor used in the sample application is shown below. You need to declare WebTrackerEJB as a message-driven bean and the message destination type as javax.jms.Queue. You can ignore this configuration if you have annotated the MDB in Java EE 5 style.


<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
  <display-name>Web Tracker App</display-name>
  <enterprise-beans> 
    <message-driven>   
        <display-name>WebTrackerEJB</display-name>   
        <ejb-name>WebTrackerEJB</ejb-name>   
        <ejb-class>demo.webtracker.ejb.WebTrackerEJB</ejb-class>
      <transaction-type>Container</transaction-type>
        <message-destination-type>
        javax.jms.Queue
      </message-destination-type>
    </message-driven>
  </enterprise-beans>
  <assembly-descriptor> 
    <container-transaction>   
        <method>
          <ejb-name>WebTrackerEJB</ejb-name>     
          <method-name>*</method-name>   
        </method>   
          <trans-attribute>Required</trans-attribute> 
      </container-transaction>
  </assembly-descriptor>
</ejb-jar>

sun-ejb-jar.xml

The sun-ejb-jar.xml deployment descriptor used in the sample application is shown below. The JNDI names of the connection factory and queue will be declared in this descriptor. You can ignore this configuration if you have annotated the MDB in Java EE 5 style.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.
//DTD Application Server 9.0 EJB 3.0//EN"
"http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">
<sun-ejb-jar>
  <enterprise-beans>
    <ejb>
      <ejb-name>WebTrackerEJB</ejb-name>
      <jndi-name>webTrackerQueue</jndi-name>
      <mdb-connection-factory>
          <jndi-name>webTrackerConnFactory</jndi-name>
      </mdb-connection-factory>
    </ejb>
  </enterprise-beans>
</sun-ejb-jar>

Testing the Application

Now the application is deployed successfully and it is ready for testing. The home page of the application can be accessed using the following URL:

http://<host name>:<port number>/webTracker

A snapshot of the home page is shown in Figure 1.

Home page of Web Access Tracker
Figure 1. Home page of Web Access Tracker

The web access report can be viewed by clicking the Web Access Info link provided on the left navigation bar. This report is shown in Figure 2.

Web Access Report
Figure 2. Web access report

Conclusion

In this article we have explored JMS messaging and GlassFish's messaging capabilities. There is no doubt that JMS has earned wide industry support and emerged as a powerful solution for enterprise messaging. The use case explained here truly demonstrates the beauty of JMS to solve realtime issues using messaging solutions.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值