Oracle Application Server Containers for J2EE Enterprise JavaBeans Developer's Guide 10g (9.0.4) Part Number B10324-01 |
|
The following sections discuss the tasks in creating an MDB in Oracle Application Server Containers for J2EE (OC4J) and demonstrate MDB development with a basic configuration to use either OC4J JMS or Oracle JMS as the JMS provider.
Download the MDB example used in this chapter from the OC4J sample code page on the OTN Web site.
A Message-Driven Bean (MDB) is a Java Message Service (JMS) message listener that can reliably consume messages from a queue or a topic. An MDB uses the asynchronous nature of a JMS listener with the benefit of the EJB container, which does the following:
QueueReceiver
or TopicSubscriber
for the listener.
QueueReceiver
or TopicSubscriber
, and its factory.
Within normal JMS objects, a JMS message listener exists and must explicitly specify the consumer and its factory within its code. When you use MDBs, the container specifies the consumer and its factory for you; thus, an MDB is an easy method for creating a JMS message listener. You still have to retrieve the objects and create them given the interface, but the container does most of the work for you.
The OC4J MDB interacts with a JMS provider. This chapter highlights two JMS providers, OC4J JMS and Oracle JMS, each of which must be installed and configured appropriately.
Note: A full description of how to use each JMS provider is discussed in the JMS chapter in the Oracle Application Server Containers for J2EE Services Guide. In addition, for information on security, see theOracle Application Server Containers for J2EE Security Guide. |
The following are generic steps to create and enable an MDB with a JMS provider:
Destination
objects for the MDB, and connection details for the MDB where the provider is installed.
Destination
objects used in its deployment descriptors.
This chapter describes how to implement each of these steps with both the OC4J JMS and Oracle JMS providers. Each section uses an MDB example that is available for download from the OC4J sample code page on the OTN Web site.
The main MDB implementation and the EJB deployment descriptor can be the same for both JMS types and is shown in the "MDB Example". The OC4J-specific deployment descriptor for this MDB and the JMS configuration is different for each JMS type, so these are described specifically in each of the provider sections.
The MDB can process incoming asynchronous requests. Any message for the MDB is routed to the onMessage
method of the MDB from the queue or topic. Other clients may have access to the same queue or topic to send messages for the MDB. Most MDBs receive messages from a queue or a topic, then invoke an entity bean to process the request contained within the message.
The steps to create an MDB, which are shown in the following sections, are as follows:
Destination
used in the EJB deployment descriptor (ejb-jar.xml
). Define if any durable subscriptions or message selectors are used. See "EJB Deployment Descriptor (ejb-jar.xml) for the MDB" for details.
ejb-jar.xml
file and map them to their actual JNDI names in the OC4J-specific deployment descriptor (orion-ejb-jar.xml
).
onMessage
method in the <container-transaction>
element in the ejb-jar.xml
file. All of the steps for an MDB should be in the onMessage
method. Since the MDB is stateless, the onMessage
method should perform all duties. Do not create the JMS connection and session in the ejbCreate
method. However, if you are using OracleAS JMS, then you can optimize your MDB by creating the JMS connection and session in the ejbCreate
method and destroying them in the ejbRemove
method.
application.xml
file, create an EAR file, and install the EJB in OC4J.
The MDB implementation and the ejb-jar.xml
deployment descriptor can be exactly the same for the OC4J JMS or Oracle JMS providers--if you use resource references for the JNDI lookup of the connection factory and the Destination
object. The orion-ejb-jar.xml
deployment descriptor contains provider-specific configuration, including the mapping of the resource references. See "MDB Using OC4J JMS" and "MDB Using Oracle JMS" for the specific configuration in the orion-ejb-jar.xml
deployment descriptor.
Note: The example used for the MDB example uses resource references, so that the MDB is generic. If you want to see how to explicitly define a JNDI string for each JMS provider, see "Client Access of MDB", as the client uses both explicit JNDI strings as well as resource references. |
The major points to do when you implement an MDB are as follows:
public
(not final
or abstract
).
javax.ejb.MessageDrivenBean
and javax.jms.MessageListener
interfaces, which include the following:
The following MDB example--rpTestMdb
MDB--prints out a message sent to it through a queue and responds. The queue is identified in the deployment descriptors and the JMS configuration. In the onMessage
method, the MDB creates a new message to be sent to the client. It sets the message selector property RECIPIENT
to be for the CLIENT
. Then, it sets the reply destination and sends the new message to the JMS client.
This example shows how to receive a message from a queue and send out a response. You can receive a message in several ways. This example uses the methods of the Message
object to retrieve all attributes of the message.
To send out a response to a queue, you must first set up a sender, which requires the following:
jms/myQueueConnectionFactory
," which is defined in the ejb-jar.xml
file and mapped to the actual JNDI name in the orion-ejb-jar.xml
file.
createQueueConnection
method of the QueueConnectionFactory object.
createQueueSession
method of the QueueConnection
object.
createSender
method of the QueueSession
object.
These steps are implemented as follows:
private QueueConnection m_qc = null; private QueueSession m_qs = null; private QueueSender m_snd = null; QueueConnectionFactory qcf = (QueueConnectionFactory) ctx.lookup("java:comp/env/jms/myQueueConnectionFactory"); m_qc = qcf.createQueueConnection(); m_qs = m_qc.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); m_snd = m_qs.createSender(null);
Once the sender is created, you can send any message using the send
method of the QueueSender
object. This example puts together a response from the received message and then use the sender to send out that response.
createMessage
method of the Message
object.
Message
object, such as setStringProperty
and setIntProperty
.
getJMSReplyTo
method of the Message
object. The destination was initialized in the message by the sender.
send
method of the QueueSender
object. Provide the destination and the response message.
Message rmsg = m_qs.createMessage(); rmsg.setStringProperty("RECIPIENT", "CLIENT"); rmsg.setIntProperty("count", msg.getIntProperty("JMSXDeliveryCount")); rmsg.setJMSCorrelationID(msg.getJMSMessageID()); Destination d = msg.getJMSReplyTo(); m_snd.send((Queue) d, rmsg);
The following is the complete example of the MDB that receives a message and sends back a response.
import java.util.*; import javax.ejb.*; import javax.jms.*; import javax.naming.*; public class rpTestMdb implements MessageDrivenBean, MessageListener { private QueueConnection m_qc = null; private QueueSession m_qs = null; private QueueSender m_snd = null; private MessageDrivenContext m_ctx = null; /* Constructor, which is public and takes no arguments.*/ public rpTestMdb() { } /* setMessageDrivenContext method */ public void setMessageDrivenContext(MessageDrivenContext ctx) { /* As with all EJBs, you must set the context in order to be able to use it at another time within the MDB methods. */ m_ctx = ctx; } /* ejbCreate method, declared as public (but not final or * static), with a return type of void, and with no arguments. */ public void ejbCreate() { } /* ejbRemove method */ public void ejbRemove() { } /** * onMessage method * Receives the incoming Message and displays the text. */ public void onMessage(Message msg) { /* An MDB does not carry state for an individual client. */ try { Context ctx = new InitialContext(); // 1. Retrieve the QueueConnectionFactory using a // resource reference defined in the ejb-jar.xml file. QueueConnectionFactory qcf = (QueueConnectionFactory) ctx.lookup("java:comp/env/jms/myQueueConnectionFactory"); ctx.close(); /*You create the queue connection first, then a session over the connection. Once the session is set up, then you create a sender */ // 2. Create the queue connection m_qc = qcf.createQueueConnection(); // 3. Create the session over the queue connection. m_qs = m_qc.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); // 4. Create the sender to send messages over the session. m_snd = m_qs.createSender(null); /* When the onMessage method is called, a message has been sent. You can retrieve attributes of the message using the Message object. */ String txt = ("mdb rcv: " + msg.getJMSMessageID()); System.out.println(txt + " redel=" + msg.getJMSRedelivered() + " cnt=" + msg.getIntProperty("JMSXDeliveryCount")); /* Create a new message using the createMessage method. To send it back to the originator of the other message, set the String property of ""RECIPIENT" to "CLIENT." The client only looks for messages with string property CLIENT. Copy the original message ID into new msg's Correlation ID for tracking purposes using the setJMSCorrelationID method. Finally, set the destination for the message using the getJMSReplyTo method on the previously received message. Send the message using the send method on the queue sender. */ // 5. Create a message using the createMessage method Message rmsg = m_qs.createMessage(); // 6. Set properties of the message. rmsg.setStringProperty("RECIPIENT", "CLIENT"); rmsg.setIntProperty("count", msg.getIntProperty("JMSXDeliveryCount")); rmsg.setJMSCorrelationID(msg.getJMSMessageID()); // 7. Retrieve the reply destination. Destination d = msg.getJMSReplyTo(); // 8. Send the message using the send method of the sender. m_snd.send((Queue) d, rmsg); System.out.println(txt + " snd: " + rmsg.getJMSMessageID()); /* close the connection*/ m_qc.close(); } catch (Throwable ex) { ex.printStackTrace(); } } }
Note:
The entire MDB example is available on the |
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 define whether it is durable. If you have used resource references, define the resource reference for both the connection factory and the Destination
object.
The following example demonstrates the deployment information for the rpTestMdb MDB in the <message-driven>
element, as follows:
<ejb-name>
element.
<ejb-class>
element, which ties the <message-driven>
element to the specific MDB implementation.
Destination
type is a Queue
that is specified in the <message-driven-destination><destination-type>
element.
RECIPIENT
is MDB
.
<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.
<resource-ref>
element; the resource reference for the Destination
object is defined in the <resource-env-ref>
element. See "Using a Logical Name When Client Accesses the MDB" for a full discussion on resource references for JMS object types.
<?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> <display-name>Mdb Test</display-name> <enterprise-beans> <message-driven> <display-name>testMdb</display-name> <ejb-name>testMdb
</ejb-name> <ejb-class>rpTestMdb
</ejb-class> <transaction-type>Container
</transaction-type> <message-selector>RECIPIENT='MDB'
</message-selector> <message-driven-destination> <destination-type>javax.jms.Queue
</destination-type> </message-driven-destination> <resource-ref> <description>description</description> <res-ref-name>jms/myQueueConnectionFactory</res-ref-name> <res-type>javax.jms.QueueConnectionFactory</res-type> <res-auth>Application</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> </resource-ref> <resource-env-ref> <resource-env-ref-name>jms/persistentQueue
</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref> </message-driven> </enterprise-beans> <assembly-descriptor><container-transaction>
<method> <ejb-name>testMdb</ejb-name> <method-name>onMessage
</method-name> <method-params> <method-param>javax.jms.Message</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
If you were going to configure a durable Topic
instead, then the <message-driven-destination>
element would be configured as follows:
<message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>Durable</subscription-durability> </message-driven-destination>
Note:
The entire MDB example is available on the |
The OC4J-specific deployment descriptor (orion-ejb-jar.xml
) for this MDB and the JMS provider configuration necessary is shown in the following sections:
Instructions on how a client sends a JMS message to the MDB is discussed in "Client Access of MDB".
The MDB can process incoming asynchronous requests using OC4J JMS. When you use OC4J JMS, this JMS provider is already available since it is bundled with OC4J. And all configuration for the JMS provider occurs within the OC4J XML files; thus, only steps three and four (as listed in "MDB Overview") are necessary.
Note:
The entire MDB example is available on the |
Figure 7-1 shows how a client sends an asynchronous request directly to the OC4J JMS queue or topic that is located internally within OC4J. The MDB receives the message directly from OC4J JMS.
Text description of the illustration mdb2.gif
The following sections demonstrate an MDB that uses OC4J JMS as the JMS provider.
Note: A full description of how to use each JMS provider is available in the JMS chapter in the Oracle Application Server Containers for J2EE Services Guide. |
OC4J JMS is automatically enabled. You only configure the JMS Destination
objects used by the MDB. If your MDB accesses a database for inquiries and so on, then you can configure the DataSource
used. See "JMS Destination Object Configuration" for the JMS configuration. For information on data source configuration, see the Data Source chapter in the Oracle Application Server Containers for J2EE Services Guide.
Configure the topic or queue in the jms.xml
file to which the client sends all messages that are destined for the MDB. The name, location, and connection factory for either Destination
type must be specified.
The following jms.xml
file configuration specifies a queue--named jms/Queue/rpTestQueue
--that is used by the rpTestMdb example. The queue connection factory is defined as jms/Queue/myQCF
. In addition, a topic is defined named jms/Topic/rpTestTopic
, with a connection factory of jms/Topic/myTCF
.
<?xml version="1.0" ?> <!DOCTYPE jms-server PUBLIC "OC4J JMS server" "http://xmlns.oracle.com/ias/dtds /jms-server.dtd"> <jms-server port="9128"> <queue location="jms/Queue/rpTestQueue"> </queue> <queue-connection-factory location="jms/Queue/myQCF"> </queue-connection-factory> <topic location="jms/Topic/rpTestTopic"> </topic> <topic-connection-factory location="jms/Topic/myTCF"> </topic-connection-factory> <!-- path to the log-file where JMS-events/errors are stored --> <log> <file path="../log/jms.log" /> </log> </jms-server>
The OC4J-specific deployment descriptor configures the following:
Destination
and connection factory JNDI locations to the MDB through the <message-driven-deployment>
element in the orion-ejb-jar.xml
file. See "Specify the Destination and Connection Factory" for full details.
ejb-jar.xml
file to the correct queue or topic, which, for OC4J JMS, is defined in the jms.xml
file. You could have several topics and queues defined in the jms.xml
file. See "Map Any Resource References to JNDI Names" for full details on mapping the resource references in the orion-ejb-jar.xml
file.
Since this example uses resource references in the ejb-jar.xml
file, the orion-ejb-jar.xml
file maps these logical names to the actual JNDI names of the connection factory and the JMS Destination
object, which are defined in the jms.xml
file. In this example, the MDB uses a queue that is defined in the jms.xml
file as jms/Queue/rpTestQueue
. The queue connection factory is defined in the jms.xml
file as jms/Queue/myQCF
.
Map the Destination
and connection factory JNDI locations 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 rpTestMdb
example. It maps a JMS Queue
to the rpTestMdb
MDB, providing the following:
<ejb-name>
in the EJB deployment descriptor, is specified in the name
attribute.
Destination
, as defined in the jms.xml
file, is specified in the destination-location
attribute.
Destination
Connection
Factory
, as defined in the jms.xml
file, is specified in the connection-factory-location
attribute.
subscription-name
attribute.
listener-threads
attribute, is an optional parameter. 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. Topics always have only one thread.
Once all of these are specified in the <message-driven-deployment>
element, the container knows how to map the MDB to the correct JMS Destination
.
<enterprise-beans>...
<message-driven-deployment name="rpTestMdb"
connection-factory-location="jms/Queue/myQCF"
destination-location="jms/Queue/rpTestQueue" >
</message-driven-deployment>
...
</enterprise-beans>
If you wanted to specify a topic, you must also include the subscription name, as follows:
<enterprise-beans> <message-driven-deployment name="rpTestMdb"connection-factory-location="jms/Queue/myQCF"
destination-location="jms/Queue/rpTestQueue"
subscription-name="MDBSUB" > ... </enterprise-beans>
When you define logical names as resource references for your connection factory and Destination
object, you have to map these to the actual JNDI names.
<resource-ref-mapping>
element. In the rpTestMdb
example, the logical name for the connection factory is jms/myQueueConnectionFactory
. This must be mapped to the JNDI string of jms/Queue/myQCF
, which is defined in the jms.xml
file.
Destination
object in the <resource-env-ref-mapping>
element. In the rpTestMdb
example, the logical name for the queue is jms/persistentQueue
. This is mapped to the JNDI string of jms/Queue/rpTestQueue, which is defined in the jms.xml
file.
<resource-ref-mapping name="jms/myQueueConnectionFactory" location="jms/Queue/
myQCF"/> <resource-env-ref-mapping name="jms/persistentQueue" location="jms/Queue/
rpTestQueue" />
The following lists the complete orion-ejb-jar.xml
file for the rpTestMdb
example. It includes both the definition of the OC4J JMS objects and the resource reference mappings.
<enterprise-beans> <message-driven-deployment name="testMdb" connection-factory-location="jms/Queue/myQCF" destination-location="jms/Queue/rpTestQueue" listener-threads="1"> <resource-ref-mapping name="jms/myQueueConnectionFactory" location="jms/Queue/myQCF"/> <resource-env-ref-mapping name="jms/persistentQueue" location="jms/Queue/rpTestQueue" /> </message-driven-deployment> </enterprise-beans> <assembly-descriptor> <default-method-access> <security-role-mapping name="<default-ejb-caller-role>" impliesAll="true" /> </default-method-access> </assembly-descriptor>
Archive your EJB into a JAR file. You deploy the MDB the same way as the session bean, which is detailed in "Prepare the EJB Application for Assembly" and "Deploy the Enterprise Application to OC4J".
Note: Instructions on how a client sends a JMS message to the MDB is discussed in "Client Access of MDB". |
The MDB processes incoming asynchronous requests using Oracle JMS (Advanced Queuing), as follows:
Warning: MDBs only work with certain versions of the Oracle database. See the certification matrix in the JMS chapter of the Oracle Application Server Containers for J2EE Services Guide for more information. |
onMessage
method of the MDB.
At any time, the client can send a message to the Oracle JMS topic or queue on which MDBs are listening. The Oracle JMS topic or queue is located in the database.
Note:
The entire MDB example is available on the |
Text description of the illustration mdba.gif
The following sections demonstrate an MDB that uses Oracle JMS as the JMS provider.
Note: A full description of how to use Oracle JMS provider is discussed in the JMS chapter in the Oracle Application Server Containers for J2EE Services Guide. Also, see the Oracle9i Application Developer's Guide - Advanced Queuing. |
You or your DBA must install Oracle JMS according to theOracle9i Application Developer's Guide--Advanced Queuing for Release 2 (9.2) and generic database manuals. Once you have installed and configured this JMS provider, you must apply additional configuration for each MDB. This includes the following:
Destination
objects. See "Create JMS Destination Objects".
Note: The following sections use SQL for creating queues, topics, their tables, and assigning privileges that is provided within the MDB demo on the OC4J sample code page on the OTN Web site. |
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 theOracle9i Application Developer's Guide--Advanced Queuing for Release 2 (9.2) for more information on privileges necessary for each type of function.
The following example creates jmsuser
, which must be created within its own schema, with privileges required for Oracle JMS operations. You must be a SYS
DBA
to execute these statements.
DROP USER jmsuser CASCADE ; GRANT connect, resource,AQ_ADMINISTRATOR_ROLE TO jmsuser IDENTIFIED BY jmsuser ; GRANT execute ON sys.dbms_aqadm TO jmsuser; GRANT execute ON sys.dbms_aq TO jmsuser; GRANT execute ON sys.dbms_aqin TO jmsuser; GRANT execute ON sys.dbms_aqjms TO jmsuser; connect jmsuser/jmsuser;
You may need to grant other privileges, such as two-phase commit or system administration privileges, based on what the user needs. See the JTA chapter in the Oracle Application Server Containers for J2EE Services Guide for the two-phase commit privileges.
Each JMS provider requires its own method for creating the JMS Destination
object. Refer to theOracle9i Application Developer's Guide--Advanced Queuing for Release 2 (9.2) for more information on the DBMS_AQADM packages and Oracle JMS messages types. For our example, Oracle JMS requires the following methods:
Note: The SQL for creating the tables for the Oracle JMS example is included in the MDB example available on the OC4J sample code page on the OTN Web site. |
Destination
(queue or topic).
In Oracle JMS, both topics and queues use a queue table. The rpTestMdb
JMS example creates a single table: rpTestQTab for a queue.
To create the queue table, execute the following SQL:
DBMS_AQADM.CREATE_QUEUE_TABLE( Queue_table => 'rpTestQTab', Queue_payload_type => 'SYS.AQ$_JMS_MESSAGE', sort_list => 'PRIORITY,ENQ_TIME', multiple_consumers => false, compatible => '8.1.5');
The multiple_consumers
parameter denotes whether there are multiple consumers or not; thus, is always false for a queue and true for a topic.
Destination
. If you are creating a topic, you must add each subscriber for the topic. The rpTestMdb
JMS example requires a single queue--rpTestQueue
.
The following creates a queue called rpTestQueue
within the queue table rpTestQTab
. After creation, the queue is started.
DBMS_AQADM.CREATE_QUEUE( Queue_name => 'rpTestQueue', Queue_table => 'rpTestQTab'); DBMS_AQADM.START_QUEUE( queue_name => 'rpTestQueue');
If you wanted to add a topic, then the following example shows how you can create a topic called rpTestTopic
within the topic table rpTestTTab. After creation, two durable subscribers are added to the topic. Finally, the topic is started and a user is granted a privilege to it.
DBMS_AQADM.CREATE_QUEUE_TABLE( Queue_table => 'rpTestTTab', Queue_payload_type => 'SYS.AQ$_JMS_MESSAGE', multiple_consumers => true, compatible => '8.1.5'); DBMS_AQADM.CREATE_QUEUE( 'rpTestTopic', 'rpTestTTab'); DBMS_AQADM.ADD_SUBSCRIBER('rpTestTopic',
sys.aq$_agent('MDSUB', null, null)); DBMS_AQADM.ADD_SUBSCRIBER('rpTestTopic',
sys.aq$_agent('MDSUB2', null, null)); DBMS_AQADM.START_QUEUE('rpTestTopic');
To use the Oracle JMS provider, you must configure the following in the OC4J XML files:
Configure a data source for the database where the Oracle JMS provider is installed. The JMS topics and queues 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 an emulated data source that uses the thin JDBC driver. To support a two-phase commit transaction, use a non-emulated data source. For differences between emulated and non-emulated data sources, see the Data Source chapter in the Oracle Application Server Containers for J2EE Services Guide.
The example is displayed in the format of an XML definition; see the Oracle Application Server Containers for J2EE User's Guide for directions on adding a new data source to the configuration through the EM tool.
<data-source class="com.evermind.sql.DriverManagerDataSource" name="OracleDS" location="jdbc/emulatedOracleCoreDS" xa-location="jdbc/xa/emulatedOracleXADS" ejb-location="jdbc/emulatedDS" connection-driver="oracle.jdbc.driver.OracleDriver" username="jmsuser" password="jmsuser" url="jdbc:oracle:thin:@myhost.foo.com:1521:orcl" />
Customize this data source to match your environment. For example, substitute the host name, port, and SID of your database for mysun:1521:orcl
.
Note: Instead of providing the password in the clear, you can use password indirection. For details, see the Oracle Application Server Containers for J2EE Services Guide. |
Identify the JNDI name of the data source that is to be used as the Oracle JMS 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 JMS provider using XML syntax for Oracle JMS.
class
attribute--The Oracle JMS 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 JMS 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/emulatedDS
" is to be used as the Oracle JMS provider. This JNDI name is identified in the ejb-location
element in Example 7-4. If this example used a non-emulated data source, then the name would be the same as in the location
element.
<resource-provider class="oracle.jms.OjmsContext" name="myProvider"> <description> OJMS/AQ </description> <property name="datasource" value="jdbc/emulatedDS"></property> </resource-provider>
The OC4J-specific deployment descriptor configures the following:
Destination
and connection factory JNDI locations to the MDB through the <message-driven-deployment>
element in the orion-ejb-jar.xml
file. See "Specify the Destination and Connection Factory" for full details.
ejb-jar.xml
file to the correct queue or topic, which, for Oracle JMS, was defined in the database through SQL. You could have several topics and queues defined in database. See "Map Any Resource References to JNDI Names" for full details on mapping the resource references in the orion-ejb-jar.xml
file.
Since this example uses resource references in the ejb-jar.xml
file, the orion-ejb-jar.xml
file maps these logical names to the actual JNDI names of the connection factory and the JMS Destination
object, which are defined in the database. In this example, the MDB uses a queue that is defined in the database as rpTestQueue
. The queue connection factory is not defined in the database, so any name can be used. For consistency, the queue connection factory name is myQCF
.
Map the Destination
and connection factory JNDI locations 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 rpTestMdb
example. It maps a JMS Queue
to the rpTestMdb
MDB, providing the following:
<message-driven><ejb-name>
in the EJB deployment descriptor, is specified in the name
attribute.
Destination
Connection
Factory
, as specified by the user, is specified in the connection-factory-location
attribute. The Oracle JMS syntax for the connection factory is "java:comp/resource
" + JMS provider name + "TopicConnectionFactories
" or "QueueConnectionFactories
" + a user defined name. The user-defined name can be anything and does not match any other configuration. The xxxConnectionFactories
details what type of factory is being defined. For this example, the JMS provider name is defined in the <resource-provider>
element in the application.xml
file as myProvider
.
myProvider
and you decide to use a name of myQCF
, the connection factory name is "java:comp/resource/myProvider/QueueConnectionFactories/myQCF".
myProvider
and you decide to use a name of myTCF
, the connection factory name is "java:comp/resource/myProvider/TopicConnectionFactories/myTCF".
The user defined names, as shown above by myQCF
and myTCF
, are not used for anything else in your logic. So, any name can be chosen.
Destination
, as defined in the database, is specified in the destination-location
element. The Oracle JMS syntax for the Destination
is "java:comp/resource
" + JMS 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 JMS provider name is defined in the <resource-provider>
element in the application.xml
file as myProvider
. In the database, the topic name is rpTestQueue
.
myProvider
and the queue name is rpTestQueue, then the JNDI name for the queue as "java:comp/resource/myProvider/Queues/rpTestQueue."
myProvider
and the topic name is rpTestTopic
, then the JNDI name for the topic as "java:comp/resource/myProvider/Topics/rpTestTopic."
subscription-name
attribute.
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. Topics always use only one thread; queues can use more than one.
transaction-timeout
attribute, is an optional parameter. This attribute controls the transaction timeout interval (in seconds) for any container-managed transactional MDB. The default is one day or 86,400 seconds. If the transaction has not completed in this time frame, the transaction is rolled back and the message is redelivered back to the Destination
object. JMS attempts to redeliver the message (defaults to five attempts and is set on the DBMS_AQADM.CREATE_QUEUE
method when creating the queue in the database), after which the message is moved to the exception queue. You can browse messages in the exception queue using SQL*Plus. For more information on setting redelivery attempts and browsing the exception queue, refer to theOracle9i Application Developer's Guide--Advanced Queuing for Release 2 (9.2).
Once all of these are specified in the <message-driven-deployment>
element, the container knows how to map the MDB to the correct JMS Destination
.
<message-driven-deployment name="testMdb" connection-factory-location=
"java:comp/resource/myProvider/QueueConnectionFactories/myQCF" destination-location="java:comp/resource/myProvider/Queues/rpTestQueue" listener-threads="5">
If you wanted to specify a topic, you must also include the subscription name, as follows:
<enterprise-beans> <message-driven-deployment name="rpTestMdb" connection-factory-location= "java:comp/resource/myProvider/TopicConnectionFactories/myTCF" destination-location="java:comp/resource/cartojms1/Topics/rpTestTopic" subscription-name="MDBSUB" listener-threads=1 > ... </enterprise-beans>
When you define logical names as resource references for your connection factory and Destination
object, you have to map these to the actual JNDI names.
<resource-ref-mapping>
element. In the rpTestMdb
example, the logical name for the connection factory is jms/myQueueConnectionFactory
. This must be mapped to the JNDI string of java:comp/resource/myProvider/QueueConnectionFactories/myQCF.
Destination
object in the <resource-env-ref-mapping>
element. In the rpTestMdb
example, the logical name for the queue is jms/persistentQueue
. This is mapped to the JNDI string of java:comp/resource/myProvider/Queues/rpTestQueue.
See "Specify the Destination and Connection Factory" for how the Oracle JMS JNDI syntax was derived.
<resource-ref-mapping name="jms/myQueueConnectionFactory" location="java:comp/resource/myProvider/QueueConnectionFactories/myQCF"/> <resource-env-ref-mapping name="jms/persistentQueue" location="java:comp/resource/myProvider/Queues/rpTestQueue" />
The following lists the complete orion-ejb-jar.xml
file for the rpTestMdb
example. It includes both the definition of the Oracle JMS objects and the resource reference mappings.
<enterprise-beans> <message-driven-deployment name="testMdb" connection-factory-location=
"java:comp/resource/myProvider/QueueConnectionFactories/myQCF" destination-location="java:comp/resource/myProvider/Queues/rpTestQueue" listener-threads="5"> <resource-ref-mapping name="jms/myQueueConnectionFactory" location="java:comp/resource/myProvider/QueueConnectionFactories/myQCF"/> <resource-env-ref-mapping name="jms/persistentQueue" location="java:comp/resource/myProvider/Queues/rpTestQueue" /> </message-driven-deployment> </enterprise-beans> <assembly-descriptor> <default-method-access> <security-role-mapping name="<default-ejb-caller-role>" impliesAll="true" /> </default-method-access> </assembly-descriptor>
Archive your MDB 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.
Note: Instructions on how a client sends a JMS message to the MDB is discussed in "Client Access of MDB". |
The client sends a message to the MDB through a JMS Destination
. The client can retrieve the JMS Destination
and connection factory either through using its explicit name or by a logical name. The following sections describe both methods for retrieving the JNDI name.
Note: You may have to add the JNDI properties if the client is not co-located with the MDB. The examples provided in the following sections do not include setting the JNDI properties. See "Client Implementation to Invoke the EJB" for instructions on setting these properties. |
Within your client, you can use the actual JNDI name to retrieve the JMS Destination
objects. Both OC4J JMS and Oracle JMS have their own naming methodology, as explained in the following sections:
Note:
Alternatively, you can specify all of the JNDI names for the |
The JNDI lookup for OC4J JMS requires the OC4J JMS Destination
and connection factory as defined by you within the jms.xml
file, prepended with "java:comp/env/
." See "JMS Destination Object Configuration" to see how the queue and topic for OC4J JMS is configured.
Note: If you decide to use logical names instead, you would use the same JNDI syntax. Logical names are recommended, because they are portable. See "Using a Logical Name When Client Accesses the MDB" for more information. |
To lookup a queue in the JNDI lookup for the testResourceProvider example using OC4J JMS are as follows:
//Lookup the Queue queue = (Queue)jndiContext.lookup("java:comp/env/jms/Queue/rpTestQueue"); //Lookup the Queue Connection factory queueConnectionFactory = (QueueConnectionFactory) jndiContext.lookup("java:comp/env/jms/Queue/myQCF");
To lookup a topic, you would have slightly different strings, designating a topic rather than a queue, as follows:
//Lookup the Topic topic = (Topic)jndiContext.lookup("java:comp/env/jms/Topic/rpTestTopic"); //Lookup the Connection factory topicConnectionFactory = (TopicConnectionFactory) jndiContext.lookup("java:comp/env/jms/Topic/myTCF");
Note that the same names for the topic and the connection factory are used in the client's configuration, the jms.xml
, and the MDB deployment descriptors.
The JNDI lookup--when using Oracle JMS--requires the Oracle JMS Destination
and connection factory syntax, which is the same naming convention as described for the connection-factory-location
and destination-location
attributes in "Specify the Destination and Connection Factory".
Note: If you decide to use logical names instead, you would use the same JNDI syntax. See "Using a Logical Name When Client Accesses the MDB" for more information. |
In your JNDI lookup, the implementation would be as follows for both a queue and a topic (See Example 7-6 for the full example):
/* Retrieve an Oracle JMS Queue through JNDI */ queue = (Queue) ic.lookup("java:comp/resource/myProvider/Queues/rpTestQueue"); /*Retrieve the Oracle JMS Queue connection factory */ queueConnectionFactory = (QueueConnectionFactory) ic.lookup ("java:comp/resource/myProvider/QueueConnectionFactories/myQCF"); /* Retrieve an Oracle JMS Topic through JNDI */ topic = (Topic) ic.lookup("java:comp/resource/myProvider/Topics/rpTestTopic"); /*Retrieve the Oracle JMS Topic connection factory */ topicConnectionFactory = (TopicConnectionFactory) ic.lookup ("java:comp/resource/myProvider/TopicConnectionFactories/myTCF");
Whether or not the implementation uses logical names or the actual JNDI names, the client sends a JMS message to the MDB by doing 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.
public final class testResourceProvider extends HttpServlet { private String resProvider = "myResProvider"; private HashMap msgMap = new HashMap(); Context ctx = new InitialContext(); public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { //Retrieve the name of the JMS provider from the request, which is // to be used in creating the JNDI string for retrieval String rp = req.getParameter ("provider"); if (rp != null) resProvider = rp; try {// 1a. Look up the Queue Connection Factory
QueueConnectionFactory qcf = (QueueConnectionFactory) ctx.lookup ("java:comp/resource/" + resProvider + "/QueueConnectionFactories/myQCF"); // 1b. Lookup the Queue Queue queue = (Queue) ctx.lookup ("java:comp/resource/" + resProvider + "/Queues/rpTestQueue"); // 2 & 3. Retrieve a connection and a session on top of the connection // 2a. Create queue connection using the connection factory. QueueConnection qconn = qcf.createQueueConnection(); // 2a. We're receiving msgs, so start the connection. qconn.start(); // 3. create a session over the queue connection. QueueSession qsess = qconn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); // 4. Since this is for a queue, create a sender on top of the session. //This is used to send out the message over the queue. QueueSender snd = sess.createSender (q); drainQueue (sess, q); TextMessage msg = null; /* Send msgs to queue. */ for (int i = 0; i < 3; i++) { // 5. Create message msg = sess.createTextMessage(); msg.setText ("TestMessage:" + i); // set property of the recipient to be the MDB
//and set the reply destination. msg.setStringProperty ("RECIPIENT", "MDB"); msg.setJMSReplyTo(q); //6. send the message using the sender. snd.send (msg); // You can store the messages IDs and sent-time in a map (msgMap), // so that when messages are received, you can verify if you // *only* received those messages that you were // expecting. See receiveFromMDB() method where msgMap gets used. msgMap.put (msg.getJMSMessageID(), new Long (msg.getJMSTimestamp())); } // receive a reply from the MDB. receiveFromMDB (sess, q); //7. Close sender, session, and connection for queue snd.close(); sess.close(); qconn.close(); } catch (Exception e) { System.err.println ("** TEST FAILED **"+ e.toString()); e.printStackTrace(); } finally { } } /* * Receive any msgs sent to us via the MDB */ private void receiveFromMDB (QueueSession sess, Queue q) throws Exception { //The MDB sends out a message (as a reply) to this client. The MDB sets // the receipient as CLIENT. Thus, we will only receive msgs that have // RECIPIENT set to 'CLIENT' QueueReceiver rcv = sess.createReceiver (q, "RECIPIENT = 'CLIENT'"); int nrcvd = 0; long trtimes = 0L; long tctimes = 0L; // First msg needs to come from MDB. May take a little while //Receiving Messages for (Message msg = rcv.receive (30000); msg != null; msg = rcv.receive (30000)) { nrcvd++; String rcp = msg.getStringProperty ("RECIPIENT"); // Verify if msg in message Map // We check the msgMap to see if this is the message that we are // expecting. String corrid = msg.getJMSCorrelationID(); if (msgMap.containsKey(corrid)) { msgMap.remove(corrid); } else { System.err.println ("** received unexpected message [" + corrid + "] **"); } } rcv.close(); } /* * Drain messages from queue */ private int drainQueue (QueueSession sess, Queue q) throws Exception { QueueReceiver rcv = sess.createReceiver (q); int nrcvd = 0; /* * First drain any old msgs from queue */ for (Message msg = rcv.receive(1000); msg != null; msg = rcv.receive(1000)) nrcvd++; rcv.close(); return nrcvd; } }
If you want to use a logical name in your client application code, then define the logical name in one of the following XML files:
application-client.xml
file
ejb-jar.xml
file
web.xml
file
Map the logical name to the actual name of the topic or queue name in the OC4J deployment descriptors.
You can create logical names for the connection factory and Destination
objects, as follows:
<resource-ref>
element.
<res-ref-name>
element.
<res-type>
element as either 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
--the topic or queue--is identified in a <resource-env-ref>
element.
The following shows an example of how to specify logical names for a topic.
<resource-ref> <res-ref-name>myTCF</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>rpTestTopic</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 descriptors. The actual names, or JNDI names, are different in OC4J JMS than in Oracle JMS. However, the mapping is defined in one of the following files:
orion-application-client.xml
orion-ejb-jar.xml
orion-web.xml
file.
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.
See the following sections for how the mapping occurs for both OC4J JMS and Oracle JMS:
The JNDI name for the OC4J JMS Destination and connection factory is defined by you within the jms.xml
file. As shown in "JMS Destination Object Configuration", the JNDI names for the topic and the topic connection factory are as follows:
jms/Topic/rpTestTopic
."
jms/Topic/myTCF
."
Prepend both of these names with "java:comp/env/
" and you have the mapping in the orion-ejb-jar.xml
file as follows:
<resource-ref-mapping
name="myTCF"
location="java:comp/env/jms/Topic/myTCF"> </resource-ref-mapping> <resource-env-ref-mapping
name="rpTestTopic"
location="java:comp/env/jms/Topic/rpTestTopic">
</resource-env-ref-mapping>
The JNDI naming for Oracle JMS Destination
and connection factory objects is the same name that was specified in the orion-ejb-jar.xml
file for the MDB as described in "Specify the Destination and Connection Factory".
The following example maps the logical names for the connection factory and topic to their actual JNDI names. Specifically, the topic defined logically as "rpTestTopic
" in the ejb-jar.xml
file is mapped to its JNDI name of "java:comp/resource/cartojms1/Topics/rpTestTopic
."
<resource-ref-mapping
name="myTCF"
location="java:comp/resource/myProvider/TopicConnectionFactories/myTCF"> </resource-ref-mapping> <resource-env-ref-mapping
name="rpTestTopic"
location="java:comp/resource/myProvider/Topics/rpTestTopic">
</resource-env-ref-mapping>
Once the resources have been defined, the client sends a JMS message to the MDB by doing 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 method of sending a message over a topic is almost the same. Instead of creating a queue, you create a topic. Instead of creating a sender, you create subscribers.
The following JSP client code sends a message over a topic to the MessageBean
MDB. The code uses logical names, which should be mapped in the OC4J deployment descriptor.
<%@ page import="javax.jms.*, javax.naming.*, java.util.*" %> <% //1a. Lookup the MessageBean topic jndiContext = new InitialContext(); topic = (Topic)jndiContext.lookup("rpTestTopic"); //1b. Lookup the MessageBean Connection factory topicConnectionFactory = (TopicConnectionFactory) jndiContext.lookup("myTCF"); //2 & 3. Retrieve a connection and a session on top of the connection topicConnection = topicConnectionFactory.createTopicConnection(); topicSession = topicConnection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); //5. Create the publisher for any messages destined for the topic topicPublisher = topicSession.createPublisher(topic); //6. Send out the message for (int ii = 0; ii < numMsgs; ii++) { message = topicSession.createBytesMessage(); String sndstr = "1::This is message " + (ii + 1) + " " + item; byte[] msgdata = sndstr.getBytes(); message.writeBytes(msgdata); topicPublisher.publish(message); System.out.println("--->Sent message: " + sndstr); } //7. Close publisher, session, and connection for topic topicPublisher.close(); topicSession.close(); topicConnection.close(); %> Message sent!
The oracle.mdb.fastUndeploy
system property enables you to shutdown OC4J cleanly when you are running MDBs in a Windows environment or when the backend database is running on a Windows environment. Normally, when you use an MDB, it is blocked in a receive state waiting for incoming messages. However, if you shutdown OC4J while the MDB is in a wait state in a Windows environment, then the OC4J instance cannot be stopped and the applications are not undeployed since the MDB is blocked. However, you can modify the behavior of the MDB in this environment by setting the oracle.mdb.fastUndeploy
system property. If you set this property to an integer, then when the MDB is not processing incoming messages and in a wait state, the OC4J container goes out to the database (requiring a database round-trip) and polls to see if the session is shut down. The integer denotes the number of seconds the system waits to poll the database. This can be expensive for performance. If you set this property to 60 (seconds), then every 60 seconds, OC4J is checking the database. If you do not set this property and you try to shutdown OC4J using CTRL-C, the OC4J process will hang for at least 2.5 hours.
An application that uses an RAC database must handle database failover scenarios. The MDB run time does not fail over to the newly available database. To enable failover, the deployment descriptors dequeue-retry-count
and dequeue-retry-interval
must be specified in orion-ejb-jar.xml
file. The first parameter, dequeue-retry-count
, tells the container how many times to retry the database connection in case a failure happens; the default is 0. The second parameter, dequeue-retry-interval
, tells the container how long to wait between attempts (to accommodate for the time it takes for database failover); the default value is 60 (seconds).
Note: The RAC-enabled attribute of a data source is discussed in Data Sources chapter in the Oracle Application Server Containers for J2EE Services Guide. (RAC is real application clusters. For more information on using this flag with an infrastructure database, see the Oracle Application Server 10g High Availability Guide.) |
These parameters are attributes of the <message-driven-deployment>
element, as shown in the following example:
<message-driven-deployment name="MessageBeanTpc" connection-factory-location= "java:comp/resource/cartojms1/TopicConnectionFactories/aqTcf" destination-location= "java:comp/resource/cartojms1/Topics/topic1" subscription-name="MDBSUB" dequeue-retry-count=3 dequeue-retry-interval=90/>
A standalone OJMS client running against an RAC database must write similar code to obtain the connection again, by invoking the API DbUtil.oracleFatalError()
, to determine if the connection object is invalid. It must then reestablish the database connection if necessary. The following example outlines the logic:
getMessage(QueueSesssion session) { try { QueueReceiver rcvr; Message msgRec = null; QueueReceiver rcvr = session.createReceiver(rcvrQueue); msgRec = rcvr.receive(); } catch(Exception e ) { if (exc instanceof JMSException) { JMSException jmsexc = (JMSException) exc; sql_ex = (SQLException)(jmsexc.getLinkedException()); db_conn = (oracle.jms.AQjmsSession)session.getDBConnection(); if ((DbUtil.oracleFatalError(sql_ex, db_conn)) { // failover logic } } } }
|
![]() Copyright © 2002, 2003 Oracle Corporation. All Rights Reserved. | | Ad Choices. |
|