Oracle9iAS Containers for J2EE Enterprise JavaBeans Developer's Guide Release 2 (9.0.3) Part Number A97677-01 |
|
A Message-Driven Bean (MDB) is a Java Messaging Service (JMS) message listener that can reliably consume messages from a queue or a subscription of a topic. The advantage of using an MDB instead of a JMS message listener is that you can use the asynchronous nature of a JMS listener with the benefit of the EJB container performing the following:
QueueReceiver
or TopicSubscriber
is created by the container.
QueueReceiver
or TopicSubscriber
and its factory at deployment time.
An MDB is an easy method for creating a JMS message listener.
The following sections discuss the tasks in creating an MDB in Oracle9iAS Containers for J2EE (OC4J) and demonstrate MDB development with a basic configuration to use Oracle JMS as the resource provider.
Download the MDB example from the OC4J sample code page on the OTN web site.
An MDB is a unique EJB whose function is to read or write JMS messages from a JMS Destination
(topic or queue).
The OC4J MDB interacts with Oracle JMS, which must be installed and configured appropriately. Oracle JMS is installed and configured on an Oracle database. Within this database, the appropriate queue or table is created.
onMessage
method of the MDB from the queue or topic. Other clients may have access to the same queue or topic to put on messages for the MDB.
MDBs interact with queues and topics furnished by the Oracle JMS resource provider. A full description of how to use this resource provider is discussed in the JMS chapter in the Oracle9iAS Containers for J2EE Services Guide.
The JMS chapter details the following steps that enable each resource provider:
Destination
objects.
To create an MDB that uses the resource provider, perform the following steps:
javax.ejb.MessageDrivenBean
and javax.jms.MessageListener
interfaces, which includes the following:
Destination
used in the EJB deployment descriptor. Define if any durable subscriptions are used.
Destination
type to the MDB in the OC4J-specific deployment descriptor--orion-ejb-jar.xml
.
onMessage
method in the <container-transaction>
element.
application.xml
file, create an EAR file, and install the EJB in OC4J.
The following sections demonstrate a simple MDB, using Oracle JMS as the resource provider. For directions on configuring other resource providers, see the JMS chapter in the Oracle9iAS Containers for J2EE Services Guide.
Before you can use the MDB within the application, you must choose and configure a resource provider for the JMS Destination
objects used by the MDB. The following sections discuss how to configure Oracle JMS.
Create an RDBMS user through which the MDB connects to the database. Grant access privileges to this user to perform Oracle JMS operations. The privileges that you need depend on what functionality you are requesting. Refer to the Oracle9i Application Developer's Guide - Advanced Queuing for more information on privileges necessary for each type of function.
The following example creates MYUSER
with privileges required for Oracle JMS operations:
create user MYUSER identified by MYPASSWORD; grant connect, resource to MYUSER; grant execute on sys.dbms_aqadm to MYUSER; grant execute on sys.dbms_aq to MYUSER; grant execute on sys.dbms_aqin to MYUSER; grant execute on sys.dbms_aqjms to MYUSER; connect MYUSER/MYPASSWORD;
You may need to grant other privileges, such as two-phase commit (requires FORCE
ANY
TRANSACTION
) or system administration privileges, based on what the user needs.
Each resource provider requires its own method for creating the JMS Destination
object. Refer to the Oracle9i Application Developer's Guide - Advanced Queuing for more information on the DBMS_AQADM packages and Oracle JMS messages types. For our example, Oracle JMS requires the following methods:
Destination
(queue or topic).
In Oracle JMS, both topics and queues use a queue table. The Oracle JMS example within this chapter creates two tables: QTque
for a queue and QTtpc
for a topic.
To create the queue table, execute the following SQL:
DBMS_AQADM.CREATE_QUEUE_TABLE( Queue_table => 'QTque', Queue_payload_type => 'SYS.AQ$_JMS_BYTES_MESSAGE', multiple_consumers => false);
The type of message is defined as one of the following:
SYS.AQ$_JMS_BYTES_MESSAGE
SYS.AQ$_JMS_MAP_MESSAGE
SYS.AQ$_JMS_STRING_MESSAGE
SYS.AQ$_JMS_TEXT_MESSAGE
SYS.AQ$_JMS_OBJECT_MESSAGE
The third parameter denotes whether there are multiple consumers or not; thus, is always false for a queue and true for a topic.
To create the topic table, execute the following SQL:
DBMS_AQADM.CREATE_QUEUE_TABLE( Queue_table => 'QTtpc', Queue_payload_type => 'SYS.AQ$_JMS_BYTES_MESSAGE', multiple_consumers => TRUE);
Destination
. If you are creating a topic, you must add each subscriber for the topic. This example chooses to use durable subscribers, which results in additional configuration within the deployment descriptors.
The Oracle JMS example within this chapter requires a single topic with two subscribers--topic1
with MDBSUB
and MDBSUB2
--and a single queue--queue1
.
The following creates a topic called topic1
within the topic table QTtpc
with max retries set to 2. After creation, two durable subscribers are added to the topic. Finally, the topic is started.
DBMS_AQADM.CREATE_QUEUE( 'topic1', 'QTtpc'); DBMS_AQADM.ADD_SUBSCRIBER('topic1', sys.aq$_agent('MDSUB', null, null)); DBMS_AQADM.ADD_SUBSCRIBER('topic1', sys.aq$_agent('MDSUB2', null, null)); DBMS_AQADM.START_QUEUE('topic1');
The following creates a queue called queue1
within the queue table QTque
with max retries set to 2. After creation, the queue is started.
DBMS_AQADM.CREATE_QUEUE( 'queue1' , 'QTque'); DBMS_AQADM.START_QUEUE('queue1');
Configure the Oracle JMS resource provider by configuring a data source. The topics and queues connect to the database and use database tables and queues to facilitate messaging.
The type of data source you use depends on the functionality you want.
For no transactions or single-phase transactions, you can use either an emulated or non-emulated data sources. For two-phase commit transaction support, you can use only a non-emulated data source.
The following example contains a non-emulated data source that uses the thin JDBC driver. It can support a two-phase commit transaction; it cannot support session pooling.
The example is displayed in the format of an XML definition; see the Oracle9iAS Containers for J2EE User's Guide for directions on adding a new data source to the configuration.
<data-source class="com.evermind.sql.OrionCMTDataSource" name="myDS" location="jdbc/MyDS" connection-driver="oracle.jdbc.driver.OracleDriver" username="myuser" password="mypasswd" url="jdbc:oracle:thin:@myhost.foo.com:1521:mydb" inactivity-timeout="30" />
Identify the JNDI name of the data source that is to be used as the resource provider within the <resource-provider>
element.
application.xml
file.
orion-application.xml
file of the application.
The following code sample shows how to configure the resource provider using XML syntax for Oracle JMS.
class
attribute--The Oracle JMS resource provider is implemented by the oracle.jms.OjmsContext
class, which is configured in the class
attribute.
property
attribute--Identify the data source that is to be used as this resource provider in the property
element. The topic or queue connects to this data source to access the tables and queues that facilitate the messaging.
The following example demonstrates that the data source identified by "jdbc/CartEmulatedDS
" is to be used as the Oracle JMS resource provider. This JNDI name is identified in the ejb-location
element in Example 7-1.
<resource-provider class="oracle.jms.OjmsContext" name="cartojms1"> <description> OJMS/AQ </description> <property name="datasource" value="jdbc/myDS"></property> </resource-provider>
Most MDBs receive messages from a queue or a topic, then invoke an entity bean to process the request contained within the message.
The following example is a MessageBean
MDB. Its functionality is to print out a message sent to it through a durable topic. The topic is identified in the deployment descriptors.
As an MDB, it is responsible for the following:
javax.ejb.MessageDrivenBean
and javax.jms.MessageListener
interfaces
public
(not final
or abstract
)
setMessageDrivenContext
, ejbCreate
, onMessage
, and ejbRemove
package cart.ejb; import com.evermind.server.ThreadState; import java.io.Serializable; import java.rmi.RemoteException; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.ejb.CreateException; import javax.naming.*; import javax.transaction.*; import javax.jms.*; import oracle.AQ.*; import oracle.jms.*; public class MessageBean implements javax.ejb.MessageDrivenBean, javax.jms.MessageListener { private transient MessageDrivenContext mdbCtx = null; /* Constructor, which is public and takes no arguments.*/ public MessageBean() { } /* setMessageDrivenContext method */ public void setMessageDrivenContext(MessageDrivenContext mdc) { /* As with all EJBs, you must set the context in order to be able to use it at another time within the MDB methods. */ this.mdbCtx = mdc; } /* ejbCreate method, declared as public (but not final or * static), with a return type of void, and with no arguments. */ public void ejbCreate() throws Exception { /* no implementation is necessary for this MDB */ /* An MDB does not carry state for an individual client. However, you can retrieve state for use across many calls for multiple clients - state such as an entity bean reference or a database connection. If so, retrieve these within the ejbCreate and remove them in the ejbRemove method. */ } /* ejbRemove method */ public void ejbRemove() { /* no implementation is necessary for this MDB*/ } /** * onMessage method * Casts the incoming Message to a TextMessage and displays * the text. */ public void onMessage(Message msg) { /* The whole point for this message MDB is to receive and print messages. It is not complicated, but it shows how MDBs are set up to receive JMS messages from queues and topics. */ BytesMessage msgBytes = null; try { /* This message was created as a JMS BytesMessage. */ if (msg instanceof BytesMessage) { /* Convert the BytesMessage into printable text... */ msgBytes = (BytesMessage) msg; byte[] msgdata = ((AQjmsBytesMessage) msgBytes).getBytesData(); String txt = new String(msgdata); /* Print out message */ System.out.println("Message received=" + txt); } } catch (Exception e) { throw new RuntimeException("onMessage throws exception"); } } }
The deployment descriptors define MDB configuration in the <message-driven>
element.
ejb-jar.xml
) specifies whether a queue or a topic is used. This example uses a durable topic.
orion-ejb-jar.xml
) associates the queue or topic with the actual JMS Destination
created in the resource provider.
Within the EJB deployment descriptor (ejb-jar.xml
), define the MDB name, class, JNDI reference, and JMS Destination
type (queue or topic) in the <message-driven>
element. If a topic is specified, you must also define whether it is durable.
The following example demonstrates the deployment information for the MessageBean
MDB in the <message-driven>
element, as follows:
<ejb-name>
element.
<ejb-class>
element.
Destination
type is a Topic
that is specified in the <message-driven-destination><jms-destination-type>
element.
<message-driven-destination><subscription-durability>
element. Options are "Durable
" or "nonDurable
."
<transaction-type>
element. The value can be Container
or Bean
. If Container
is specified, define the onMessage
method within the <container-transaction>
element with the type of CMT support.
<?xml version="1.0" encoding="UTF-8"?> <!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> <description>A demo cart bean package.</description> <display-name>A simple cart jar</display-name> <enterprise-beans> ... <message-driven> <description></description> <display-name>MessageBeanTpc</display-name> <ejb-name>MessageBeanTpc</ejb-name> <ejb-class>cart.ejb.MessageBean</ejb-class> <transaction-type>Container</transaction-type> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>Durable</subscription-durability> </message-driven-destination> </message-driven> ... <assembly-descriptor> <container-transaction> <method> <ejb-name>MessageBeanTpc</ejb-name> <method-name>onMessage</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> ... </assembly-descriptor> </enterprise-beans>
Once you have configured the MDB and the JMS Destination
type, inform the container which JMS Destination
to associate with the MDB. To identify the Destination
that is to be associated with the MDB, map the Destination
location and connection factory to the MDB through the <message-driven-deployment>
element in the orion-ejb-jar.xml
file.
The following is the orion-ejb-jar.xml
deployment descriptor for the MessageBean
example. It maps an Oracle JMS Topic
to the MessageBean
MDB, providing the following:
<message-driven><ejb-name>
in the EJB deployment descriptor, is specified in the name
attribute.
Destination
Connection
Factory
is specified in the connection-factory-location
attribute. The syntax is "java:comp/resource
" + resource provider name + "TopicConnectionFactories
" or "QueueConnectionFactories
" + user defined name. The xxxConnectionFactories
details what type of factory is being defined. For this example, the resource provider name is defined in the <resource-provider>
element in the application.xml
file as cartojms1
and the user defined name is aqTcf
.
Destination
is specified in the destination-location
attribute. The syntax is "java:comp/resource
" + resource provider name + "Topics
" or "Queues
" + Destination
name. The Topic
or Queue
details what type of Destination
is being defined. The Destination
name is the actual queue or topic name defined in the database.
For this example, the resource provider name is defined in the <resource-provider>
element in the application.xml
file as cartojms1
. In this example, the topic name is topic1
.
Csubscription-name
attribute. Two subscriptions were created from the SQL in the database: MDBSUB
and MDBSUB2
. This topic uses the MDBSUB
subscription.
listener-threads
attribute. The listener threads are spawned off when MDBs are deployed and are used to listen for incoming JMS messages on the topic or queue. These threads concurrently consume JMS messages. The default is one thread.
transaction-timeout
attribute. This attribute controls the transaction timeout interval for any container-managed transactional MDB. The default is one day. If the transaction has not completed in this timeframe, the transaction is rolled back.
After you specify all of these in the <message-driven-deployment>
element, the container knows how to map the MDB to the correct JMS Destination
.
<enterprise-beans> <message-driven-deployment connection-factory-location= "java:comp/resource/cartojms1/TopicConnectionFactories/aqTcf" name="MessageBeanTpc" destination-location="java:comp/resource/cartojms1/Topics/topic1" subscription-name="MDBSUB" listener-threads=50 transaction-timeout=172800> ... </enterprise-beans>
Archive your EJB into a JAR file. You deploy the MDB in the same way as the session bean, which "Prepare the EJB Application for Assembly" and "Deploy the Enterprise Application to OC4J" describe.
The client sends a message to the MDB through a JMS Destination
. The MDB is associated with the JMS Destination
by the container.
To send a JMS message to an MDB, perform the following:
Destination
and its connection factory using a JNDI lookup.
Destination
, create a sender for a queue, or a publisher for a topic.
Destination
types.
The following code sends a message over a topic to the MessageBean
MDB.
Context ic = new InitialContext(); /*1. Retrieve an Oracle JMS Topic and connection factory through JNDI */ topic = (Topic) ic.lookup("java:comp/resource/ojms/Topics/topic1"); /*Retrieve the Oracle JMS Topic connection factory */ topicConnectionFactory = (TopicConnectionFactory) ic.lookup ("java:comp/resource/ojms/TopicConnectionFactories/aqTcf"); /*2. Create a Topic connection */ topicConnection = topicConnectionFactory.createTopicConnection(); /*3. Create a Topic session over the connection */ topicSession = topicConnection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); /*4. Create a publisher to send a message to the MDB -- The createPublisher method is invoked off of the session object, but requires the retrieved topic as its input. */ topicPublisher = topicSession.createPublisher(topic); /*5. Create the message to send to the MDB */ message = topicSession.createTextMessage(); for (int i = 0; i < NUM_MSGS; i++) { message.setText("This is message " + (i + 1)); System.out.println("Sending message: " + message.getText()); /*6. Send the message using the topic publisher */ topicPublisher.publish(message); } /*7. After message is sent, close the connection */ topicConnection.close();
If you have another EJB acting as the MDB client, you can retrieve the connection factory and the JMS Destination
using logical references that have been configured in the client-side deployment descriptor.
The following defines logical names in the client-side deployment descriptor. If the client is a true Java client, this would be in its application-client.xml
file. If the client is another EJB, these additions would be added in the ejb-jar.xml
file.
<resource-ref>
element.
<res-ref-name>
element.
<res-type>
element: javax.jms.QueueConnectionFactory or javax.jms.TopicConnectionFactory.
Container
or Bean
) is defined in the <res-auth>
element.
Shareable
or Unshareable
) is defined in the <res-sharing-scope>
element.
Destination
is defined in a <resource-env-ref>
element.
The following shows an example of how to define a queue and a topic.
<resource-ref> <res-ref-name>jms/Queue/senderQueueConnectionFactory</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/Queue/senderQueue</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref> <resource-ref> <res-ref-name>jms/Topic/senderTopicConnectionFactory</res-ref-name> <res-type>javax.jms.TopicConnectionFactory</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> </resource-ref> <resource-env-ref> <resource-env-ref-name>jms/Topic/senderTopic</resource-env-ref-name> <resource-env-ref-type>javax.jms.Topic</resource-env-ref-type> </resource-env-ref>
Then, you map the logical names to actual names in the OC4J deployment descriptor. If the client is a true Java client, these additions would be within the orion-application-client.xml
. If the client is another EJB, these additions are added to the orion-ejb-jar.xml
.
The logical names in the client's deployment descriptor are mapped as follows:
<resource-ref>
element is mapped to its JNDI name in the <resource-ref-mapping>
element.
Destination
defined in the <resource-env-ref>
element is mapped to its JNDI name in the <resource-env-ref-mapping>
element.
The following example maps the logical names for the connection factories, topic and queue defined above in the client deployment descriptor to their actual JNDI names. Specifically, the topic defined logically as "jms/Topic/senderTopic
" in the ejb-jar.xml
file is mapped to its JNDI name of "java:comp/resource/cartojms1/Topics/topic1
."
<session-deployment name="MyCart" max-instances="10" location="MyCart"> <resource-ref-mapping
name="jms/Topic/senderTopicConnectionFactory"
location="java:comp/resource/cartojms1/TopicConnectionFactories/aqTcf"> </resource-ref-mapping> <resource-env-ref-mapping
name="jms/Topic/senderTopic"
location="java:comp/resource/cartojms1/Topics/topic1">
</resource-env-ref-mapping> <resource-ref-mapping
name="jms/Queue/senderQueueConnectionFactory"
location="java:comp/resource/cartojms1/QueueConnectionFactories/aqQcf"> </resource-ref-mapping> <resource-env-ref-mapping
name="jms/Queue/senderQueue"
location="java:comp/resource/cartojms1/Queues/queue1">
</resource-env-ref-mapping> </session-deployment>
Once the mapping is complete, you can modify your JNDI lookup to use the logical name, as follows:
Context ic = new InitialContext(); /*Retrieve an Oracle JMS Topic and connection factory through JNDI */ topic = (Topic) ic.lookup("jms/Topic/senderTopic"); /*Retrieve the Oracle JMS Topic connection factory */ topicConnectionFactory = (TopicConnectionFactory) ic.lookup ("jms/Topic/senderTopicConnectionFactory");
|
![]() Copyright © 2002 Oracle Corporation. All Rights Reserved. |
|