Skip Headers

Oracle Application Server Containers for J2EE Enterprise JavaBeans Developer's Guide
10g (9.0.4)

Part Number B10324-01
Go To Documentation Library
Home
Go To Table Of Contents
Contents
Go To Index
Index

Go to previous page Go to next page

7
Message-Driven Beans

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.

MDB Overview

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:

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.

The following are generic steps to create and enable an MDB with a JMS provider:

  1. Install the JMS provider.

  2. Configure the JMS provider, the Destination objects for the MDB, and connection details for the MDB where the provider is installed.

  3. Configure OC4J with the JMS provider details in the OC4J XML files.

  4. Implement the MDB and map the JMS 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.

MDB Example

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:

  1. Implement the bean, as shown in "MDB Example".

  2. Create the MDB deployment descriptors.

    1. Define the JMS connection factory and 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.

    2. If using resource references, define these in the ejb-jar.xml file and map them to their actual JNDI names in the OC4J-specific deployment descriptor (orion-ejb-jar.xml).

    3. If the MDB uses container-managed transaction demarcation, specify the 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.

  3. Create an EJB JAR file containing the bean and the deployment descriptors. Configure the application-specific 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.


MDB Implementation Example

The major points to do when you implement an MDB are as follows:


Note:

See the EJB specification for the full details on all aspects of implementing a MDB.


  1. The bean class must be defined as public (not final or abstract).

  2. The bean class must implement the javax.ejb.MessageDrivenBean and javax.jms.MessageListener interfaces, which include the following:

    • the onMessage method in the MessageListener interface

    • the setMessageDrivenContext method in the MessageDrivenBean interface

  3. The bean class must implement the container callback methods that normally match methods in the EJB home interface. Remote, local, and home interfaces are not implemented with an MDB. However, some of the callback methods required for these interfaces are implemented in the bean implementation. These methods include the following:

    • an ejbCreate method

    • an ejbRemove method

Example 7-1 MDB Implementation

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:

  1. Retrieve the QueueConnectionFactory object. This example uses a resource reference of "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.

  2. Create the JMS queue connection using the createQueueConnection method of the QueueConnectionFactory object.

  3. Create a JMS session over the connection using the createQueueSession method of the QueueConnection object.

  4. Once the session is set up, then create a sender that uses the session through the 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.

  1. Create a message using the createMessage method of the Message object.

  2. Set properties of the message using methods of the Message object, such as setStringProperty and setIntProperty.

  3. This example retrieves the destination for its response through the getJMSReplyTo method of the Message object. The destination was initialized in the message by the sender.

  4. Send out the response using the sender through the 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);
    

Example 7-2 MDB Implementation

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
OC4J sample code page on the OTN Web site.


EJB Deployment Descriptor (ejb-jar.xml) for the MDB

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:

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
OC4J sample code page on the OTN Web site.


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".

MDB Using OC4J JMS

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
OC4J sample code page on the OTN Web site.


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.

Figure 7-1 Demonstration of an MDB Interacting with an OC4J JMS Destination

Text description of mdb2.gif follows.

Text description of the illustration mdb2.gif

The following sections demonstrate an MDB that uses OC4J JMS as the JMS provider.

Configure OC4J JMS in the XML files

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.

JMS Destination Object Configuration

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>

Create the OC4J-Specific Deployment Descriptor (orion-ejb-jar.xml) to Use OC4J JMS

The OC4J-specific deployment descriptor configures the following:

Specify the Destination and Connection Factory

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:

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>


Note:

You cannot use logical names in these fields. You must specify the full JNDI syntax for both the connection factory and the Destination object.


Map Any Resource References to JNDI Names

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.

Example 7-3 The orion-ejb-jar.xml file for the rpTestMdb Example

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="&lt;default-ejb-caller-role&gt;"
                           impliesAll="true" />
  </default-method-access>
</assembly-descriptor>

Deploying the MDB

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".


MDB Using Oracle JMS

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.


  1. The MDB opens a JMS connection to the database using a data source with a username and password. The data source represents the Oracle JMS provider and uses a JDBC driver to facilitate the JMS connection.

  2. The MDB opens a JMS session over the JMS connection.

  3. Any message for the MDB is routed to the 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
OC4J sample code page on the OTN Web site.


Figure 7-2 Demonstration of an MDB Interacting with an Oracle JMS Destination

Text description of mdba.gif follows.

Text description of the illustration mdba.gif

The following sections demonstrate an MDB that uses Oracle JMS as the JMS provider.

Install and Configure the JMS Provider

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:

  1. You or your DBA should create an RDBMS user through which the MDB connects to the database. Grant this user appropriate access privileges to perform Oracle JMS operations. See "Create User and Assign Privileges".

  2. You or your DBA should create the tables and queues to support the JMS 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 User and Assign Privileges

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.

Create JMS Destination Objects

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.


  1. Create the tables that handle the JMS 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.

  2. Create the JMS 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.


    Note:

    Oracle AQ uses the DBMS_AQADM.CREATE_QUEUE method to create both queues and topics.


    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');


    Note:

    The names defined here must be the same names used to define the queue or topic in the orion-ejb-jar.xml file.


Configure the OC4J XML Files for the JMS Provider

To use the Oracle JMS provider, you must configure the following in the OC4J XML files:

Configure the DataSource

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.

Transactional Functionality

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.

Example 7-4 Emulated DataSource With Thin JDBC Driver

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 Oracle JMS Data Source

Identify the JNDI name of the data source that is to be used as the Oracle JMS provider within the <resource-provider> element.

The following code sample shows how to configure the JMS provider using XML syntax for Oracle JMS.

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>

Create the OC4J-Specific Deployment Descriptor to Use Oracle JMS

The OC4J-specific deployment descriptor configures the following:

Specify the Destination and Connection Factory

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:

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>


Note:

You cannot use logical names in these fields. You must specify the full JNDI syntax for both the connection factory and the Destination object.


Map Any Resource References to JNDI Names

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.

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" />

Example 7-5 The orion-ejb-jar.xml file for the rpTestMdb Example

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="&lt;default-ejb-caller-role&gt;" impliesAll="true" /> </default-method-access> </assembly-descriptor>

Deploy the MDB

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".


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.

Using an Explicit Name for the JNDI Lookup

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:

Accessing OC4J JMS Destination with Explicit JNDI Names

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.

Accessing Oracle JMS Destination with Explicit JNDI Names

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");

Steps for Sending a Message to an MDB

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:

  1. Retrieve both the configured JMS Destination and its connection factory using a JNDI lookup.

  2. Create a connection from the connection factory. If you are receiving messages for a queue, then start the connection.

  3. Create a session over the connection.

  4. Providing the retrieved JMS Destination, create a sender for a queue, or a publisher for a topic.

  5. Create the message.

  6. Send out the message using either the queue sender or the topic publisher.

  7. Close the queue session. Close the connection for either JMS Destination types.

Example 7-6 Servlet Client Sends Message to Queue

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; } }

Using a Logical Name When Client Accesses the MDB

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:

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:

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:

The logical names in the client's deployment descriptor are mapped as follows:

See the following sections for how the mapping occurs for both OC4J JMS and Oracle JMS:

JNDI Naming for OC4J 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:

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>

JNDI Naming for Oracle JMS

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>

Client Sends JMS Message Using Logical Names

Once the resources have been defined, the client sends a JMS message to the MDB by doing the following:

  1. Retrieve both the configured JMS Destination and its connection factory using a JNDI lookup.

  2. Create a connection from the connection factory. If you are receiving messages for a queue, start the connection.

  3. Create a session over the connection.

  4. Providing the retrieved JMS Destination, create a sender for a queue, or a publisher for a topic.

  5. Create the message.

  6. Send out the message using either the queue sender or the topic publisher.

  7. Close the queue session. Close the connection for either JMS Destination types.

Example 7-7 JSP Client Sends Message to a Topic

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!

Windows Considerations When Using MDBs

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.

Failover Scenarios When Using a RAC Database

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
            }
          }
     }
}

Go to previous page Go to next page
Oracle
Copyright © 2002, 2003 Oracle Corporation.

All Rights Reserved.
Go To Documentation Library
Home
Go To Table Of Contents
Contents
Go To Index
Index