3 Enhanced Support for Using WebLogic JMS with EJBs and Servlets
Enabling WebLogic JMS Wrappers
WebLogic Server uses JMS wrappers that make it easier to use WebLogic JMS inside a Java EE component, such as an EJB or a servlet.
The JMS wrappers also provide a number of enhanced usability and performance features:
-
Automatic pooling of JMS connection and session objects (and some pooling of message producer objects as well)
-
Automatic transaction enlistment for WebLogic JMS implementations and for third-party JMS providers that support two-phase commit transactions (XA protocol)
-
Testing of the JMS connection, as well as reestablishment after a failure
-
Security credentials that are managed by the EJB or servlet container
The following sections provide information on how to use WebLogic JMS wrappers:
Declaring a JMSContext Object Using @Inject Annotation
WebLogic Server 12.2.1 release supports the JMS 2.0 simplified API, which enables you to inject a JMSContext
object into the application using the @Inject
annotation as follows:
@Inject @JMSConnectionFactory("myJMSCF") @JMSPasswordCredential( userName="admin", password="admin_password")private JMSContext context;
The @Inject
annotation determines when the container should create the JMSContext
object.
Note:
-
Injection should be enabled for the class. Depending on the class being used and the archive in which it is packaged, it may be necessary to specify a
beans.xml
file. For more information, see Using Contexts and Dependency Injection for the Java EE Platform in Developing Applications for Oracle WebLogic Server. -
If the injected
JMSContext
is null and if your application fails, then review the server log. If the connection factory could not be found, you can see that error in the server log. If there is no error in the server log then the application failure is probably due to a missingbeans.xml
file.
Specifying a Lookup Name in JMSContext Injection
When injecting a JMSContext
object, you can use the @JMSConnectionFactory
annotation to specify the product-specific global JNDI look up name of a connection factory to be used by the container.
Note:
When you provide a product-specific global JNDI name for the connection factory annotation, you cannot override it using a resource reference in the deployment descriptor of the container.
Alternatively, you can specify a fully qualified resource reference name of the form java:comp/env/res-ref-name
as follows:
@Inject @JMSConnectionFactory("java:comp/env/res-ref-name") private JMSContext context;
In this case, the resource reference name must be defined using a <resource-ref>
element in the deployment descriptor that maps it to an appropriate product-specific global JNDI name. See Declaring a Wrapped JMS Factory using Deployment Descriptors.
If no lookup name is provided for the @JMSConnectionFactory
annotation, then the Java EE platform default JMS connection factory (java:comp/DefaultJMSConnectionFactory
) will be used.
Determining the Authentication Type for JMSContext Injection
The JMSContext injection cannot use the resource reference to determine whether the connection factory should use container authentication or application authentication. Instead, you can use the @JMSPasswordCredential
annotation to specify the type of authentication required.
If you specify the @JMSPasswordCredential
annotation then the connection factory will use password authentication, and the specified user and password. If the @JMSPasswordCredential
annotation is not defined then the connection factory will use container authentication.
Declaring JMS Objects as Resources In the EJB or Servlet Deployment Descriptors
The following sections provide information on declaring JMS objects as resources:
For more information about packaging EJBs, see Implementing Enterprise JavaBeansin Developing Enterprise JavaBeans, Version 2.1, for Oracle WebLogic Server. For more information about programming servlets, see Creating and Configuring Servlets in Developing Web Applications, Servlets, and JSPs for Oracle WebLogic Server.
Declaring a Wrapped JMS Factory using Deployment Descriptors
Note:
New applications will likely use EJB 3.0 annotations instead of deployment descriptors. Annotations are described in Declaring JMS Destinations and Connection Factories Using Annotations.
You can declare a JMS connection factory as part of an EJB or servlet by defining a resource-ref
element in the ejb-jar.xml
or web.xml
file, respectively. This process creates a "wrapped" JMS connection factory that can benefit from the more advanced session pooling, automatic transaction enlistment, connection monitoring, and container-managed security features described in Improving Performance Through Pooling.
Here is an example of such a connection factory 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 be bound into JNDI, at the location:
java:comp/env/jms/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 signifies.
In addition to this element, there must be a matching resource-description
element in the ejb-jar.xml
(for EJBs) or weblogic.xml
(for servlets) file that tells the Java EE 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 the default JMS connection factories that is automatically created when the built-in WebLogic JMS server is used). To use another WebLogic JMS connection factory from the same cluster, 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.
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.
Declaring JMS Destinations using Deployment Descriptors
You can define a JMS destination resource in a web module, EJB module, application client module, or in an application deployment descriptor using the jms-destination
or resource-env-ref
descriptor elements.
Note:
New applications will likely use EJB 3.2 annotations instead of deployment descriptors. Annotations are described in Declaring JMS Destinations and Connection Factories Using Annotations.
The transaction enlistment, pooling, connection monitoring features take place in the connection factory, not in the destinations. However, this feature is useful for consistency, and to make an application less dependent on a particular configuration of WebLogic Server, since destinations can easily be modified by simply changing the corresponding jms-destination
or resource-env-ref
description, without having to recompile the source code
Declaring JMS Destinations Using the jms-destination Element
You can define a JMS destination resource using the jms-destination
element in the ejb-jar.xml
or web.xml
deployment descriptors. It creates the destination and binds it to the appropriate naming context based on the namespace specified.
The following example defines a queue destination myQueue1
that is bound to JNDI at the location java:app/MyJMSDestination
:
<jms-destination> <description>JMS Destination definition</description> <name>java:app/MyJMSDestination</name> <interface-name>javax.jms.Queue</interface-name> <destination-name>myQueue1</destination-name> <property> <name>Property1</name> <value>10</value> </property> <property> <name>Property2</name> <value>20</value> </property> </jms-destination>
For more information about the jms-destination
element and its attributes, see the schema at http://xmlns.jcp.org/xml/ns/javaee/javaee_7.xsd
.
Declaring JMS Destinations Using the resource-env-ref Element
You can also bind a JMS queue or topic destination into the java:comp/env
JNDI tree by declaring it as a resource-env-ref
element in the ejb-jar.xml
or web.xml
deployment descriptors.
For resource-env-ref
description, the queue or topic destination specified in the descriptor must already exist in the global JNDI tree. Again, if the destination does not exist, then the application is deployed, but an exception is thrown when you try to use the destination.
Here is an example of such a queue destination element:
<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>
This element declares that a JMS Queue
destination object will be bound into JNDI, at the location:
java:comp/env/jms/TESTQUEUE
As with a referenced connection factory, this JNDI name is only valid inside the context of the EJB or servlet where the resource-ref
is declared.
You must also define a matching resource-env-description
element in the weblogic-ejb-jar.xml
or weblogic.xml
file. This provides a layer of indirection that enables you to easily modify referenced destinations just by changing the corresponding resource-env-ref
deployment descriptors.
<resource-env-description> <resource-env-ref-name>jms/TESTQUEUE</resource-env-ref-name> <jndi-name>jmstest.destinations.TESTQUEUE</jndi-name> </resource-env-description>
Referencing a Packaged JMS Application Module In Deployment Descriptor Files
When you package a JMS module with an enterprise application, you must reference the JMS resources within the module in all applicable descriptor files of the Java EE application components, including:
-
The WebLogic enterprise descriptor file,
weblogic-application.xml
-
Any WebLogic deployment descriptor file, such as
weblogic-ejb-jar.xml
orweblogic.xml
-
Any Java EE descriptor file, such as EJB (
ejb-jar.xml
) or WebApp (web.xml
) files
Referencing Application Modules in a weblogic-application.xml Descriptor
When including JMS modules in an enterprise application, you must list each JMS module as a module element of type JMS in the weblogic-application.xml
descriptor file packaged with the application, and a path that is relative to the root of the Java EE application. Here is an example of a reference to a JMS module name Workflows:
<module> <name>Workflows</name> <type>JMS</type> <path>jms/Workflows-jms.xml</path> </module>
Referencing JMS Resources in a WebLogic Application
Within any weblogic-
foo
descriptor file, such as EJB (weblogic-ejb-jar.xml
) or WebApp (weblogic.xml
), the name of the JMS module is followed by a number (#) separator character, which is followed by the name of the resource inside the module. For example, a JMS module named Workflows that contains a queue named OrderQueue, would have a name of Workflows#OrderQueue.
<resource-env-description> <resource-env-ref-name>jms/OrderQueue</resource-env-ref-name> <resource-link>Workflows#OrderQueue</resource-link> </resource-env-description>
Note that the <resource-link>
element is unique to WebLogic Server, and is how the resources that are defined in a JMS module are referenced (linked) from the other Java EE Application components.
Referencing JMS Resources in a Java EE Application
The name
element of a JMS connection factory resource specified in the JMS module must match the res-ref-name
element defined in the referring EJB or WebApp application descriptor file. The res-ref-name
element maps the resource name (used by java:comp/env
) to a module referenced by an EJB.
For queue or topic destination resources specified in the JMS module, the name
element must match the resource-env-ref
field defined in the referring module descriptor file.
That name is how the link is made between the resource referenced in the EJB or web application module and the resource defined in the JMS module. For example:
<resource-ref> <res-ref-name>jms/OrderQueueFactory</res-ref-name> <res-type>javax.jms.ConnectionFactory</res-type> </resource-ref> <resource-env-ref> <resource-env-ref-name>jms/OrderQueue</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref>
Declaring JMS Destinations and Connection Factories Using Annotations
WebLogic Server 10.0 and later releases support the EJB 3.0 programming model which uses annotations to configure metadata, eliminating the need for deployment descriptors. You can declare JMS objects using the @Resources annotation as described in Standard JDK Annotations Used By EJB 3.0 in Developing Enterprise JavaBeans for Oracle WebLogic Server.
Injecting Resource Dependency into a Class
If you apply the @Resource to a class, then the resource is made available in the comp/env context. The following is an example of how to inject a WebLogic JMS destination and connection factory resource in a Java EE application, including EJBs, MDBs, and servlets.
Example 3-1 is a Wrapped JMS Pooling Annotation example:
Example 3-1 Wrapped JMS Pooling Annotation Example
. . . // The "name=" or "type=" are not always required, // "mappedName=" is usually sufficient. @Resource(name="ReplyQueue", type=javax.jms.Queue.class, mappedName="jms/ReplyQueue") Destination rq; . . . @Resource(name="ReplyConnectionFactory", type=javax.jms.ConnectionFactory.class, mappedName = "jms/ConnectionFactory") ConnectionFactory cf; . . .
Non-Injected EJB 3.0 Resource Reference Annotations
Injected resource dependencies are resolved when the host EJB or servlet is instantiated. You may not want injected resource because:
-
The injection may prevent applications from deploying successfully if the container attempts to resolve references during deployment.
-
You might want to defer reference resolution until the application is first invoked.
You can setup a non-injected resource reference by placing the @Resources
annotation above the class definition. An application can resolve such references at runtime by looking up the reference in the bean context. As a best practice, the bean or servlet should also cache the result in order to avoid the overhead of repeated lookups as shown in Example 3-2:
For a full example, see EJB 3.0 Wrapper Without Injection.
Example 3-2 Non-Injected Resource Example
. . . @Resources ({ @Resource(name="targetCFRef", mappedName="TargetCFJNDIName", type=javax.jms.ConnectionFactory.class), @Resource(name="targetDestRef", mappedName="TargetDestJNDIName", type=javax.jms.Destination.class) }) @Stateless(mappedName="StatelessBean") public class MyStatelessBean implements MyStateless { @Resource private SessionContext sctx; // inject the bean context private ConnectionFactory targetCF; private Destination targetDest; public void completeWorkOrder() { // Lookup the JMS resources and cache for re-use. Note that a // "java:/comp/env" prefix isn't needed for EJB3.0 bean contexts. if (targetCF == null) targetCF = (javax.jms.ConnectionFactory)sctx.lookup("targetCFRef"); if (targetDest == null) targetDest = (javax.jms.Destination)sctx.lookup("targetDestRef"); . . .
Avoid Transactional XA Interfaces
With resource wrapping, do not use the javax.jms
XA transactional XA interfaces. The container uses them internally if the JMS code is used inside a transaction context. This allows your EJB application code to run EJBs in an environment where transactions are present or in a non-transactional environment, just by changing the deployment descriptors.
Disabling Wrapping and Pooling
It is sometimes desirable to leverage resource references but disable resource reference wrapping and pooling.
To disable resource wrapping and pooling, use the deployment descriptor approach, but change the res-type
to java.lang.Object.class
in the resource-ref
stanza for the connection factory. There is currently no known way to disable wrapping and pooling using annotations.
What's Happening Under the JMS Wrapper Covers
Understand what is actually taking place under the covers when WebLogic Server creates a set of wrappers around the JMS objects.
For example, the code fragment in Sending a JMS Message in a Java EE Container, shows an instance of a WebLogic-specific wrapper class being returned, rather than the actual JMS connection factory because the connection factory was looked up from the java:comp/env
JNDI tree. This wrapper object intercepts certain calls to the JMS provider and inserts the correct Java EE behavior, as described in the following sections.
Automatically Enlisting Transactions
Automatically Enlisting Transaction works for either WebLogic JMS implementations or for third-party JMS providers that support two-phase commit transactions (XA protocol). If a wrapped JMS connection sends or receives a message inside a transaction context, then the JMS session being used to send or receive the message is automatically enlisted in the transaction through 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 whether 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 does not support XA, the send()
or receive()
call throws the following exception:
[J2EE:160055] Unable to use a wrapped JMS session in the transaction because two-phase commit is not available.
Therefore, if you are using a JMS provider that doesn't support XA to send or receive a message inside a transaction, then either declare the EJB with a transaction mode of NotSupported
or suspend the transaction using one of the JTA APIs.
Container-Managed Security
WebLogic JMS uses the security credentials that are present on the thread when the EJB or servlet container is invoked. For foreign JMS providers, however, when you declare a JMS connection factory through a resource-ref
element in the ejb-jar.xml
or web.xml
file, there is an optional sub element called res-auth
. This element may have one of two settings:
Container — When you set the res-auth
element to Container
, security to the JMS provider is managed by the Java EE 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. Otherwise, WebLogic Server connects to the provider with no user name or password specified and throws an error if the createConnection()
method is used to pass a user name and password to the connection factory.
Application — When you set the res-auth
element to Application
, any user name or password on the MBean is ignored. Instead, the application code must specify a user name and password to the createConnection(String userName, String password)
method of the JMS connection factory, or use the version of createConnection()
with no parameters if the user name or password are not required.
Note:
When you inject a JMSContext
object into the application and if the JNDI name of the connection factory is specified by @JMSConnectionFactory
, then container authentication is used. If you specify the username and password in the @JMSPasswordCredential
annotation to specify the user/password, application authentication is used. See Declaring a JMSContext Object Using @Inject Annotation.
Connection Testing
The JMS wrapper classes monitor each connection that is established to the JMS provider. They do this in two ways:
-
Registering a JMS
ExceptionListener
object on the connection. -
Testing the connection every 2 minutes by sending a message to a temporary queue or topic and then receiving it again.
Java EE Compliance
The Java EE specification states that you should not be allowed to make certain JMS API calls inside a Java EE application. The JMS wrappers enforce these restrictions by throwing the following exceptions when they are violated:
-
On the connection object, the methods
createConnectionConsumer()
,createDurableConnectionConsumer()
,setClientID()
,setExceptionListener()
, andstop()
should not be called. -
On the session object, the methods
getMessageListener()
andsetMessageListener()
should not be called. -
On the consumer object (a
QueueReceiver
orTopicSubscriber
object), the methodsgetMessageListener()
andsetMessageListener()
should not be called.
Furthermore, the createSession()
method, and the associated createQueueSession()
and createTopicSession()
methods, are handled differently. The createSession()
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 Java EE specification.
Note:
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 MDB, as described in Developing Message-Driven Beans for Oracle WebLogic Server.
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 JMS Connection Objects
The JMS wrappers pool various session objects in order to make code like the example provided in Sending a JMS Message in a Java EE Container more efficient. A pooled JMS connection is a session pool used by EJBs and servlets that use a resource-ref
element in their deployment descriptor to define their JMS connection factories, as discussed in Declaring a Wrapped JMS Factory using Deployment Descriptors.
Monitoring Pooled Connections
You can use the WebLogic Server Administration Console to monitor pooled connections. For more information, see JMS Servers: Monitoring: Active Pooled Connections in the Oracle WebLogic Server Administration Console Online Help.
Improving Performance Through Pooling
The automatic pooling of connections and other objects by the JMS wrappers means that it is efficient to write code.
For example, see the example inSending a JMS Message in a Java EE Container. 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 they are used as shown, they do little more than retrieve a Session object from the pool.
Speeding Up JNDI Lookups by Pooling Session Objects
The JNDI lookups of the Connection Factory and Destination objects can be expensive in terms of performance. 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. Because the Connection Factory and Destination objects are thread-safe, they can be looked up after they are 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 can 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. Because stateless session beans are pooled, this method is also very efficient (and is consistent with the Java EE specifications), because the number of a times that lookups occur is drastically reduced by pooling the JMS connection objects. (Caching these objects in a static member of the EJB class may work, but it is discouraged by the Java EE 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 was 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 must perform the JNDI lookup of the objects from Server B again after that server has recovered. The example, PoolTestBean.java includes a sample EJB that performs this caching and re-lookup process correctly.
Speeding Up Object Creation Through Caching
After Connection Factory object and Destination object pooling is established, 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 communication failure with the JMS provider, but this will not work if you cache the Session object on your own.
Enlisting the Proper Transaction Mode
When a JMS send()
or receive()
operation is performed inside a transaction, the EJB or servlet automatically enlists the JMS 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 throws an exception.
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, then 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, it is best to use a JMS connection factory that is not configured to support XA.
Simplified Access to Foreign JMS Providers
Learn how to access foreign JMS providers by using WebLogic Server Administration Console.
See Accessing Foreign JMS Providers in the Administering JMS Resources for Oracle WebLogic Server. This feature makes it possible to easily map foreign JMS providers — including remote instances of WebLogic Server in another cluster or domain — so that they appear in the local JNDI tree as a local JMS object.
Another set of foreign JMS provider features makes it possible to create a "symbolic link" between a JMS connection factory or destination object in an third-party JNDI provider to an object inside the local WebLogic Server. This feature can also be used to reference remote instances of WebLogic Server in another cluster or domain in the local WebLogic JNDI tree.
There are three System Module MBeans for this task:
-
Foreign server : Contains information about the remote JNDI provider, including its initial context factory, URL, and additional parameters. It is the parent of the Foreign Connection Factory and Foreign Destination MBeans. It can be targeted to an independent WebLogic Server or to a cluster. For more information see, ForeignServerBean in the MBean Reference for Oracle WebLogic Server.
-
Foreign connection factory : Represents a foreign connection factory. It contains the name of the connection factory in the remote JNDI provider, the name to map it to in the server's JNDI tree, and an optional user name and password. The user name and password are only used when a Foreign Connection Factory is used inside a
resource-reference
in an EJB or a servlet, with the "Container" mode of authentication. It creates non-replicated JNDI objects on each WebLogic Server instance to which the parent Foreign Connection Factory MBean is targeted. (To create the JNDI object on every node in a cluster, target the parent MBean to the cluster.). For more information see, ForeignConnectionFactoryBean in the MBean Reference for Oracle WebLogic Server. -
Foreign destination : Represents a foreign destination. It contains the name to look up on the foreign JNDI provider, and the name to map it to on the local server.
Examples of JMS Wrapper Functions
JMS wrapper functions make it easier to use WebLogic JMS inside a Java EE component, such as an EJB or a servlet.
Examples of JMS Wrapper Functions
The following files make up a simple stateless EJB session bean that uses the WebLogic JMS wrapper functions to send a transactional message (sendXATransactional
) when an EJB is called. Although this example uses a session bean, the same XML descriptors and bean class (with very few changes) can be used for a message-driven bean.
ejb-jar.xml
This section describes the EJB components. For the "JMS wrapper" code examples provided in this section, note that this section declares the resource-ref
and resource-env-ref
elements for the wrapped JMS connection factory (QueueConnectionFactory
) and referenced JMS destination (TESTQUEUE
).
<ejb-jar 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"> <?xml version="1.0"?> ... <ejb-jar> <enterprise-beans> <session> <ejb-name>PoolTestBean</ejb-name> <home>weblogic.jms.pool.test.PoolTestHome</home> <remote>weblogic.jms.pool.test.PoolTest</remote> <ejb-class>weblogic.jms.pool.test.PoolTestBean</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>PoolTestBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
weblogic-ejb-jar.xml
This section declares matching resource-description
queue connection factory and queue destination elements that tell the Java EE container which JMS connection factory and destination to put in that location.
<!DOC<weblogic-ejb-jar xmlns="http://www.bea.com/ns/weblogic/920" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/920 http://www.bea.com/ns/weblogic/920/weblogic-ejb-jar.xsd"> ... <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>PoolTestBean</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> <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> <jndi-name>PoolTest</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
PoolTest.java
This section defines the "remote" interface for the PoolTest
bean. It declares one method, called sendXATransactional
.
package weblogic.jms.pool.test; import java.rmi.*; import javax.ejb.*; public interface PoolTest extends EJBObject { public String sendXATransactional(String text) throws RemoteException; }
PoolTestHome.java
This section defines the "home" interface for the PoolTest
bean. It is required by the EJB specification.
package weblogic.jms.pool.test; import java.rmi.*; import javax.ejb.*; public interface PoolTestHome extends EJBHome { PoolTest create() throws CreateException, RemoteException; }
PoolTestBean.java
This section defines the actual EJB code. It sends a message whenever the sendXATransactional
method is called.
package weblogic.jms.pool.test; import java.lang.reflect.*; import java.rmi.*; import javax.ejb.*; import javax.jms.*; import javax.naming.*; import javax.transaction.*; public class PoolTestBean 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)ic.lookup ("java:comp/env/jms/QCF"); destination = (Queue)ic.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 text) 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 (text); 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 // can become invalid if the destination server has // been shut down. qcf = null; destination = null; throw new RemoteException("Failure in EJB: " + e); } return id; } }
Sending a JMS Message in a Java EE Container
After you declare the JMS connection factory and destination resources, you can use them to send and receive JMS messages inside an EJB or servlet. The following sections provide examples of how to send a message:
Using comp/env
The code in Example 3-3 sends a message if you map to the java:comp/env
JNDI tree:
Example 3-3 Sending a Message Using comp/env
. . . 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 Java EE specification and should run on any EJB or servlet product that properly supports Java EE , the difference is that it runs more efficiently on WebLogic Server, because under the covers various objects are pooled, as described in Pooled JMS Connection Objects.
Note that this code example 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 because WebLogic Server pools some of the objects that are created by this code example , it is even more important that close()
be called; otherwise, the EJB or servlet container will not know when to return the object to the pool.
Also, none of the transactional XA extensions to the JMS API are used in this code example . Instead, the container uses them internally if the JMS code is used inside a transaction context. But whether or not XA is used internally, the user-written code is the same, and does not use any JMS XA classes. This is what is specified by Java EE. Writing EJB code in this way enables you to run EJBs in an environment where transactions are present or in a non-transactional environment, just by changing the deployment descriptors.
Note:
When using a wrapped JMS connection factory, which is obtained by using the resource-ref
feature and looked up by using the java:comp/env/jms
JNDI tree context, the EJB must not use the javax.jms
XA transactional XA interfaces.
Dependency Injection
The code in Example 3-4 sends a message if you have used dependency injection to a variable.
Example 3-4 Sending a Message using Dependency Injection
package test; // Example injected annotation. import javax.annotation.Resource; import javax.ejb.*; import javax.jms.*; @Stateless(mappedName="StatelessBean") public class MyStatelessBean implements MyStateless { @Resource(mappedName="myDestJNDIName") private Destination dest; @Resource(mappedName="weblogic.jms.XAConnectionFactory") private ConnectionFactory connectionFactory; public void completeWorkOrder() { Connection con = null; Session session = null; MessageProducer sender = null; try { System.out.println("completeWorkOrder called!"); con = connectionFactory.createConnection(); session = con.createSession(true, Session.AUTO_ACKNOWLEDGE); sender = session.createProducer(null); Message message = session.createTextMessage("work order complete!"); sender.send(dest, message); } catch(Exception e) { throw new EJBException("Exception sending message: " + e, e); } finally { try { if (con != null) con.close(); } catch(Exception e) { e.printStackTrace(); } } } }
EJB 3.0 Wrapper Without Injection
Example 3-5 demonstrates EJB 3.0 annotations for an MDB that references resources that are not injected. The references are resolved at runtime when the MDB is invoked instead of when the MDB instances are instantiated.
Example 3-5 Non injected MDB Example
package test; import javax.annotation.Resources; import javax.annotation.Resource; import javax.naming.*; import javax.ejb.*; import javax.jms.*; import javax.ejb.ActivationConfigProperty; @MessageDriven( name = "MyMDB", mappedName = "JNDINameOfMDBSourceDest", activationConfig = { // the JMS interface type for the MDB destination, either javax.jms.Topic or javax.jms.Queue @ActivationConfigProperty( propertyName = "destinationType", propertyValue = "javax.jms.Queue"), // optionally specify a connection factory // there's no need to specify a connection factory if the source // destination is a WebLogic JMS destination @ActivationConfigProperty( propertyName = "connectionFactoryJndiName", propertyValue = "JNDINameOfMDBSourceCF"), }) // resources that are not injected @Resources ({ @Resource(name="targetCFRef", mappedName="TargetCFJNDIName", type=javax.jms.ConnectionFactory.class), @Resource(name="targetDestRef", mappedName="TargetDestJNDIName", type=javax.jms.Destination.class) }) public class MyMDB implements MessageListener { // inject a reference to the MDB context @Resource private MessageDrivenContext mdctx; // cache targetCF and targetDest for re-use (performance) private ConnectionFactory targetCF; private Destination targetDest; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void onMessage(Message message) { Connection jmsConnection = null; try { System.out.println("My MDB got message: " + message); if (targetCF == null) targetCF = (javax.jms.ConnectionFactory)mdctx.lookup("targetCFRef"); if (targetDest == null) targetDest = (javax.jms.Destination)mdctx.lookup("targetDestRef"); jmsConnection = targetCF.createConnection(); Session s = jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer mp = s.createProducer(null); if (message.getJMSReplyTo() != null) mp.send(message.getJMSReplyTo(), s.createTextMessage("My Reply")); else mp.send(targetDest, message); } catch (JMSException e) { throw new EJBException(e); } finally { // Return JMS resources to the resource reference pool for later re-use. // Closing a connection automatically also closes its sessions, etc. try { if (jmsConnection != null) jmsConnection.close(); } catch (JMSException ignored) {}; } } }