bea.com | products | dev2dev | support | askBEA
 Download Docs   Site Map   Glossary 
Search

Programming WebLogic JMS

 Previous Next Contents Index View as PDF  

Using WebLogic JMS with EJBs and Servlets

The following sections describe features in WebLogic Server 8.1 that make it easier to use WebLogic JMS in conjunction with J2EE components, such as a servlet or an EJB (Enterprise Java Bean).

 


Overview

This release of WebLogic Server makes it easier to use WebLogic JMS in conjunction with servlets or EJBs. These usability features are generally hidden behind the J2EE standard, but they have been enhanced for this release. Using this support should be considered as the "best practice" way to send a WebLogic JMS message from inside an EJB or servlet.

The Foreign JMS Provider Support section briefly describes the new console support for foreign JMS providers, as documented in Accessing Foreign JMS Providers section of the Administration Console Online Help. This feature makes it possible to map foreign JMS providers — including instances of WebLogic Server in another cluster or domain — so that they appear in the local JNDI tree as a local JMS object.

 


J2EE Support for WebLogic JMS

The WebLogic Server 8.1 makes it easier to use WebLogic JMS inside a J2EE component by providing usability features, such as:

These features are accessed from inside an EJB or a servlet by declaring a WebLogic JMS connection factory as a resource in the deployment descriptors. An example of this is provided in Referencing a JMS Connection Factory. Once a connection factory is registered as a resource, then the application can look it up from JNDI using the java:comp/env/ subtree that is created for each EJB or servlet. It is important to note that these features are only enabled when using a resource inside the deployment descriptors. Writers of EJBs and servlets still have direct access to the JMS provider by performing a direct JNDI lookup of the connection factory.

For more information about packaging EJBs, see "Packaging EJBs for the WebLogic Server Container" in Programming WebLogic Enterprise JavaBeans. For more information about programming servlets, see "Programming Tasks" in Programming WebLogic HTTP Servlets.

Referencing a JMS Connection Factory

A JMS connection factory can be registered as part of an EJB or servlet by including a resource-ref element in the ejb-jar.xml or web.xml file. In other words, WebLogic Server 8.1 creates a "wrapped" JMS connection factory that provides the other, more advanced features described in this section.

Here is an example of such an element:

<resource-ref>
<res-ref-name>jms/QCF</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

This element declares that a JMS QueueConnectionFactory object will be bound into JNDI, at the location:

java:comp/env/QCF

(This JNDI name is only valid inside the context of the EJB or servlet where the resource-ref is declared, which is what the java:comp/env JNDI context is about.)

In addition to this element, there must be a matching resource-description element in the weblogic-ejb-jar.xml or weblogic.xml file that tells the J2EE container which JMS connection factory to put in that location. Here is an example:

<resource-description>
<res-ref-name>jms/QCF</res-ref-name>
<jndi-name>weblogic.jms.ConnectionFactory</jndi-name>
</resource-description>

The connection factory specified here must already exist in the global JNDI tree. This example uses one of JMS connection factories that are automatically created whenever the built-in WebLogic JMS server is used. To use another WebLogic JMS connection factory from the same cluster, simply include that connection factory's JNDI name inside the jndi-name element. To use a connection factory from another vendor, or from another WebLogic Server cluster, create a Foreign JMS Server, as described in "Accessing Foreign JMS Providers" in the Administration Console Online Help.

If the JNDI name specified in the resource-description element is incorrect, then the application is still deployed. However, you will receive an error when you try to use the connection factory.

Referencing a JMS Destination

It is also possible to bind a JMS destination (a queue or topic) into the java:comp/env JNDI tree. This feature is useful for consistency, and to make an application less dependent on a particular configuration of WebLogic Server. To do this, there must be a resource-env-ref element in the weblogic-ejb-jar.xml or web.xml file, as follows:

<resource-env-ref>
<resource-env-ref-name>jms/TESTQUEUE</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
</resource-env-ref>

There must also be a matching resource-env-description element in the weblogic-ejb-jar.xml or weblogic.xml file, as follows:

<resource-env-description>
<res-env-ref-name>jms/TESTQUEUE</res-env-ref-name>
<jndi-name>jmstest.destinations.TESTQUEUE</jndi-name>
</resource-env-description>

Again, if the destination does not exist, then the application is deployed, but there will be an exception thrown when trying to use the destination.

Sending a Message

Once the resources have been mapped to the java:comp/env JNDI tree, then they can be used inside an EJB or a servlet. For instance, the following code fragment sends a message:

InitialContext ic = new InitialContext();
QueueConnectionFactory qcf =
(QueueConnectionFactory)ic.lookup("java:comp/env/jms/QCF");
Queue destQueue =
(Queue)ic.lookup("java:comp/env/jms/TESTQUEUE");
ic.close();
QueueConnection connection = qcf.createQueueConnection();
try {
QueueSession session = connection.createQueueSession(0, false);
QueueSender sender = session.createSender(destQueue);
TextMessage msg = session.createTextMessage("This is a test");
sender.send(msg);
} finally {
connection.close();
}

This is standard code that complies with the J2EE specification and which should run on any EJB product that properly supports J2EE — the difference is that it runs more efficiently on WebLogic Server 8.1, because under the covers various objects are pooled, as described in Pooled Session Objects.

Note that this code fragment uses a try...finally block to guarantee that the close() method on the JMS Connection object is executed even if one of the statements inside the block throws an exception. If no connection pooling were being done, then this block would be necessary in order to ensure that the connection is closed, and to prevent server resources from being wasted. But since WebLogic Server pools some of the objects that are created by this code fragment, it is even more important that close() be called; otherwise, the container will not know when to return the object to the pool.

Also, none of the transactional XA extensions to the JMS API are being used in this code fragment. Instead, the container uses them internally if the JMS code is used inside a transaction context. But whether XA is being used or not internally, the user-written code is the same, and does not use any of the JMS XA classes. This is what is specified by J2EE. By writing EJB code in this way, EJBs can run either in an environment where transactions are present or in a non-transactional environment, just by changing the deployment descriptors.

Under the Covers

This section explains what is happening "under the covers" when WebLogic Server creates a set of wrappers around the JMS objects. For example, in the code fragment provided in Sending a Message, since the JMS connection factory was looked up from the java:comp/env JNDI tree, the actual JMS connection factory is not being returned, but an instance of a WebLogic-specific wrapper class. This wrapper object intercepts certain calls to the JMS provider and inserts the correct J2EE behavior, as described in the following sections.

Automatically Enlisting Transactions

If a wrapped JMS connection is used to send or receive a message inside a transaction context, then the JMS session being used to send or receive the message is automatically enlisted in the transaction using the XA capabilities of the JMS provider. This is the case whether the transaction was started implicitly because the JMS code was invoked inside an EJB with container-managed transactions enabled, or if the transaction was started manually using the UserTransaction interface in a servlet or an EJB that supports bean-managed transactions.

However, if an EJB or servlet attempts to send or receive a message inside a transaction context and the JMS provider being used does not support XA, then the send() or receive() call throws an exception, as follows:

[J2EE:160055] Unable to use a wrapped JMS session in the transaction because two-phase commit is not available.

In order to send or receive a message inside a transaction using a JMS provider that does not support XA, either declare the EJB with a transaction mode of NotSupported, or suspend the transaction using one of the JTA APIs.

For more information on the attributes available when configuring a WebLogic JMS connection factory that supports transactions, see "JMS Connection Factory --> Configuration --> Transactions" in the Administration Console Online Help.

Container-Managed Security

WebLogic JMS uses the security credentials that are present on the thread when the EJB or servlet is invoked. For foreign JMS providers, however, when a JMS connection factory is declared via a resource-ref element in the weblogic-ejb-jar.xml or web.xml file, there is an optional sub-element called res-auth. This may have one of two settings:

Container — When the res-auth element is set to Container, then security to the JMS provider is managed by the J2EE container. In this case, if the JMS connection factory was mapped into the JNDI tree using a Foreign JMS Connection Factory configuration MBean, then the user name and password from that MBean is used (see Foreign JMS Provider Support ). Otherwise, WebLogic Server connects to the provider with no user name or password specified. In this mode, it is an error to pass a user name and password to the createConnection() method of the JMS connection factory.

Application — When the res-auth element is set to Application, then any user name or password on the MBean is ignored. Instead, the application code must specify a user name and password to the createConnection() method of the JMS connection factory, or use the version of this function with no user name or password if none are required.

Connection Testing

The JMS wrapper classes monitor each connection that is established to the JMS provider. They do this in two ways:

J2EE Compliance

The J2EE specification states that you should not be allowed to make certain JMS API calls inside a J2EE application. The JMS wrappers enforce these restrictions by throwing an exception when they are violated. They are as follows:

Furthermore, the createSession() method, and the associated createQueueSession() and createTopicSession() methods, are handled differently. This method takes two parameters: an "acknowledgement" mode and a "transacted" flag. When used inside an EJB, these two parameters are ignored. If a transaction is present, then the JMS session is enlisted in the transaction as described in Automatically Enlisting Transactions; otherwise, it is not. By default, the acknowledgement mode is set to "auto acknowledge". This behavior is expected by the J2EE specification. (This may make it more difficult to receive messages from inside an EJB, but the recommended way to receive messages from inside an EJB is to use a message-driven bean.)

Inside a servlet, however, the parameters to createQueueSession() and createTopicSession() are handled normally, and users can make use of all the various message acknowledgement modes.

Pooled Session Objects

The JMS wrappers pool various session objects in order to make code like the example provided in Sending a Message more efficient. A pooled JMS connection is a session pool used by EJBs and servlets that use a resource-reference element in their EJB deployment descriptor to define their JMS connection factories.

Pooled JMS sessions can be monitored using the Server --> Monitoring --> JMS node on the Administration Console. For more information, see "Server --> Monitoring --> JMS" in the Administration Console Online Help.

Improving Performance

The automatic pooling of connections and other objects by the JMS wrappers means that it is efficient to write code as shown in Sending a Message. Although in this example the Connection Factory, Connection, and Session objects are created every time a message is sent, in reality these three classes work together so that when used as shown, they do little more than retrieve a Session object from the pool.

Speeding Up JNDI Lookups

The JNDI lookups of the Connection Factory and Destination objects can be expensive. This is particularly true if the Destination object points to a Foreign JMS Destination MBean, and therefore, is a lookup on a non-local JNDI provider. Since both of these objects are thread-safe, they may be looked up once inside an EJB or servlet at creation time, which saves the time required to perform the lookup each time.

Inside a servlet, these lookups can be performed inside the init() method. The Connection Factory and Destination objects may then be assigned to an instance variable and reused whenever a message is sent.

Inside an EJB, these lookups can be performed inside the ejbCreate() method and assigned to an instance variable. For a session bean, each instance of the bean will then have its own copy, but this is perfectly fine. Since stateless session beans are pooled, this is also very efficient, and is perfectly consistent with the J2EE specifications. (Whereas, caching these objects in a static member of the EJB class may work, but it is discouraged by the J2EE specification.)

However, if these objects are cached inside the ejbCreate() or init() method, then the EJB or servlet must have some way to recreate them if there has been a failure. This is necessary because some JMS providers, like WebLogic JMS, may invalidate a Destination object after a server failure. So, if the EJB runs on Server A, and JMS runs on Server B, then the EJB on Server A will have to perform the JNDI lookup of the objects from Server B again after that server has recovered. The example, PoolTestCMPBean.java includes a sample EJB that performs this caching and relookup process correctly.

Speeding Up Object Creation

Once this has been done, it may be tempting to cache other objects, such as the Connection, Session, and Producer objects, inside the ejbCreate() method. This will work, but it is not always the most efficient solution. Essentially, by doing this you are removing a Session object from the cache and permanently assigning it to a particular EJB, whereas by using the JMS wrappers as designed, that Session object can be shared by other EJBs and servlets as well. Furthermore, the wrappers attempt to reestablish a JMS connection and create new session objects if there is a communications failure with the JMS provider, but this will not work if you cache the Session object on your own.

However, this technique will improve performance for critical code, since the management of the JMS session pool does add overhead. If you want to use this technique, you must make sure that you close and reopen the JMS connection and session objects after a server failure; otherwise, your EJB or servlet will not be able to access the JMS provider after it has been restarted.

Using the Right Transaction Mode

When a JMS send() or receive() operation is performed inside a transaction, the container automatically enlists the provider in the transaction. A transaction can be started automatically inside an EJB or servlet that has container-managed transactions, or it can be started explicitly using the UserTransaction interface. In either case, the container automatically enlists the JMS provider. However, if the underlying JMS connection factory used by the EJB or servlet does not support XA, then the container will throw an exception.

However, performing the transaction enlistment has overhead. Furthermore, if an XA connection factory is used, but the send() or receive() method is invoked outside a transaction, the container must still create a JTA transaction to wrap the send() or receive() method in order to ensure that the operation properly takes place no matter which JMS provider is used. Although this is only a one-phase commit, it can still slow down the server.

Therefore, when writing an EJB or servlet that uses a JMS resource in a non-transactional manner, then it is best to use a JMS connection factory that is not configured to support XA. For more information on configuring a WebLogic JMS connection factory, see "Configuring a JMS Connection Factory" in the Administration Console Online Help.

 


Foreign JMS Provider Support

Another set of features for WebLogic Server 8.1 makes it possible to create a "symbolic link" between a JMS connection factory or destination object in an external JNDI provider to an object inside the local WebLogic Server. There are three configuration MBeans for this task:

For instructions on configuring these MBeans with the Administration Console, refer to "Accessing Foreign JMS Providers" in the Administration Console Online Help.

Once deployed, these MBeans work by creating objects in the server's JNDI tree, which perform the lookup of the remote object every time they are looked up. This means that the local server and the remote JNDI directory are never out of sync. However, it means that a JNDI lookup of one of these MBeans can potentially be expensive. The sections on Pooled Session Objects describes some ways around this.

 


Examples of JMS Wrapper Functions

The following files comprise a simple stateless EJB session bean that uses the WebLogic JMS wrapper functions to send a message when an EJB is called. Although this example uses a session bean, the same XML descriptors and bean class, with very few changes, may be used for an message-driven bean.

ejb-jar.xml

<?xml version="1.0"?>

<!DOCTYPE ejb-jar PUBLIC
"-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/dtd/ejb-jar_2_0.dtd">

<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>PoolTestCMPBean</ejb-name>
<home>weblogic.jms.pool.test.PoolTestCMPHome</home>
<remote>weblogic.jms.pool.test.PoolTestCMP</remote>
<ejb-class>weblogic.jms.pool.test.PoolTestCMPBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>

<resource-ref>
<res-ref-name>jms/QCF</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

<resource-env-ref>
<resource-env-ref-name>jms/TESTQUEUE</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
</resource-env-ref>
</session>
</enterprise-beans>
  
 <assembly-descriptor>
<container-transaction>
<method>
<ejb-name>PoolTestCMPBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>

weblogic-ejb-jar.xml

<?xml version="1.0"?>

<!DOCTYPE weblogic-ejb-jar PUBLIC
"-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN"
"http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd">

<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>PoolTestCMPBean</ejb-name>
<stateless-session-descriptor>
<pool>
<max-beans-in-free-pool>8</max-beans-in-free-pool>
<initial-beans-in-free-pool>2</initial-beans-in-free-pool>
</pool>
</stateless-session-descriptor>

<reference-descriptor>
<resource-description>
<res-ref-name>jms/QCF</res-ref-name>
<jndi-name>weblogic.jms.XAConnectionFactory</jndi-name>
</resource-description>
<resource-env-description>
<res-env-ref-name>jms/TESTQUEUE</res-env-ref-name>
<jndi-name>TESTQUEUE</jndi-name>
</resource-env-description>
</reference-descriptor>
<jndi-name>PoolTestCMP</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>

PoolTestCMP.java

package weblogic.jms.pool.test;

import java.rmi.*;
import javax.ejb.*;
public interface PoolTestCMP extends EJBObject
{
public String sendXATransactional(String queue,
String text, int count)
throws RemoteException;
}

PoolTestCMPHome.java

package weblogic.jms.pool.test;

import java.rmi.*;
import javax.ejb.*;

public interface PoolTestCMPHome
extends EJBHome
{
PoolTestCMP create()
throws CreateException, RemoteException;
}

PoolTestCMPBean.java

package weblogic.jms.pool.test;

import java.lang.reflect.*;
import java.rmi.*;
import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;
import javax.transaction.*;

import weblogic.deployment.jms.*;

public class PoolTestCMPBean
extends PoolTestBeanBase
implements SessionBean
{
private SessionContext context;
private QueueConnectionFactory qcf;
private Queue destination;

public void ejbActivate()
{
}

public void ejbRemove()
{
}

public void ejbPassivate()
{
}

public void setSessionContext(SessionContext ctx)
{
context = ctx;
}

private void lookupJNDIObjects()
throws NamingException
{
InitialContext ic = new InitialContext();
try {
qcf =
(QueueConnectionFactory)context.lookup
("java:comp/env/jms/QCF");
destination =
(Queue)context.lookup("java:comp/env/jms/TESTQUEUE");
} finally {
ic.close();
}
}

public void ejbCreate()
throws CreateException
{
try {
lookupJNDIObjects();
} catch (NamingException ne) {
throw new CreateException(ne.toString());
}
}

public String sendXATransactional(String queue,
String text, int count)
throws RemoteException
{
String id = "Not sent yet";
try {
if ((qcf == null) || (destination == null)) {
lookupJNDIObjects();
}
QueueConnection connection = qcf.createQueueConnection();
try {
QueueSession session = connection.createQueueSession
(false, 0);
TextMessage message = session.createTextMessage
("Testing");
QueueSender sender = session.createSender(destination);
sender.send(message);
id = message.getJMSMessageID();
} finally {
connection.close();
}
} catch (Exception e) {
// Invalidate the JNDI objects if there is a failure
// this is necessary because the destination object
// may become invalid if the destination server has
// been shut down
qcf = null;
destination = null;
throw new RemoteException("Failure in EJB: " + e);
}
return id;
}
}

 

Back to Top Previous Next