Skip Headers

Oracle9i Application Developer's Guide - Advanced Queuing
Release 2 (9.2)

Part Number A96587-01
Go To Documentation Library
Home
Go To Product List
Book List
Go To Table Of Contents
Contents
Go To Index
Index

Master Index

Feedback

Go to previous page Go to next page

12
Creating Applications Using JMS

In Chapter 1, "Introduction to Oracle Advanced Queuing" we described a messaging system for an imaginary company, BooksOnLine. In this chapter we consider the features of the Oracle JMS interface to AQ in the context of a sample application based on that scenario. This chapter contains these topics:

A Sample Application Using JMS

The operations of a large bookseller, BooksOnLine, are based on an online book ordering system that automates activities across the various departments involved in the entire sale process. The front end of the system is an order entry application where new orders are entered. These incoming orders are processed by an order processing application that validates and records the order. Shipping departments located at regional warehouses are then responsible for ensuring that these orders are shipped in a timely fashion. There are three regional warehouses: one serving the East Region, one serving the West Region, and a third warehouse for shipping International orders. Once an order has been shipped, the order information is routed to a central billing department that handles payment processing. The customer service department, located at its own site, is responsible for maintaining order status and handling inquiries about orders.

In Chapter 1 we outlined a messaging system for an imaginary company, BooksOnLine. In this chapter we consider the features of the JMS interface to AQ in the context of a sample application based on that scenario. This sample application has been devised for the sole purpose of demonstrating the features of Oracle AQ. Our aim in creating this integrated scenario is to make it easier to grasp the possibilities of this technology by locating our explanations within a single context. However, it is not possible within the scope of a single relatively small code sample to demonstrate every possible application of AQ.

General Features of JMS

The following topics are discussed in this section:

J2EE Compliance

In release 9.2, Oracle JMS conforms to the Sun Microsystems JMS 1.0.2b standard. You can define the J2EE compliance mode for an OJMS client at run time. For compliance, set the Java property "oracle.jms.j2eeCompliant" to TRUE as a command line option. For noncompliance, do nothing. FALSE is the default value.

New features in release 9.2 support J2EE compliance and are also available in the noncompliant mode. These include support for:

Features of JMSPriority, JMSExpiration, and nondurable subscribers vary depending on which mode you use.

JMSPriority

JMSPriority values depend on whether you are running the default, noncompliant mode or the compliant mode, in which you set the compliance flag to TRUE:

JMSExpiration

JMSExpiration values depend on whether you are running the default, noncompliant mode or the compliant mode, in which you set the compliance flag to TRUE:

Durable Subscribers

Durable subscriber behavior, when subscribers use the same name, depends on whether you are running the default, noncompliant mode or the compliant mode, in which you set the compliance flag to TRUE.

JMS Connection and Session

Connection Factory

A ConnectionFactory encapsulates a set of connection configuration parameters that has been defined by an administrator. A client uses it to create a Connection with a JMS provider. In this case Oracle JMS, Oracle8i is the JMS Provider.

There are two types of ConnectionFactory objects

ConnectionFactory objects can be obtained in one of the following ways

  1. Static methods in AQjmsFactory
  2. Java Naming and Directory Interface (JNDI) Lookup from a LDAP directory server

Using AQjmsFactory to Obtain ConnectionFactory Objects

The AQjmsFactory class can be used to obtain a handle to Queue/Topic ConnectionFactory objects.

Example

public static void get_Factory() throws JMSException
{
  QueueConnectionFactory    qc_fact   = null;
 /* get queue connection factory for database "aqdb", host "sun-123", port 
    5521,  driver "thin" */
 qc_fact = AQjmsFactory.getQueueConnectionFactory("sun-123", "aqdb", 5521, 
                                                     "thin");
}

Using JNDI to Look Up ConnectionFactory Objects

ConnectionFactory objects can be registered in an LDAP server by a JMS administrator.

The following setup is required to enable JNDI lookup in JMS:

  1. When the Oracle9i server is installed, the database must be registered with the LDAP server. This can be done using the Database Configuration Assistant (DBCA).

    AQ entries in the LDAP server have the following structure:

    Text description of adque446.gif follows
    Text description of the illustration adque446.gif


    Connection Factory information is stored under <cn=OracleDBConnections>, while topics and queues are stored under <cn=OracleDBQueues>

  2. The GLOBAL_TOPIC_ENABLED system parameter for the database must be set to TRUE. This ensures that all queues and topics created in AQ are automatically registered with the LDAP server.

    This parameter can be set by using

    ALTER SYSTEM SET GLOBAL_TOPICS_ENABLED = TRUE

  3. After the database has been setup to use an LDAP server, the JMS administrator can register QueueConnectionFactory and TopicConnectionFactory objects in LDAP by using the AQjmsFactory.registerConnectionFactory() method.

    The registration can be done in one of the following ways:

    • Connect directly to the LDAP server - The user must have the GLOBAL_AQ_USER_ROLE to register connection factories in LDAP

      To connect directly to LDAP, the parameters for the registerConnectionFactory method include the LDAP context, the name of the Queue/Topic ConnectionFactory, hostname, database SID, port number, JDBC driver (thin or oci8) and factory type (queue or topic).

    • Connect to LDAP through the database server - the user can log on to the Oracle9i database first and then have the database update the LDAP entry. The user that logs on to the database must have the AQ_ADMINISTRATOR_ROLE to perform this operation.

      To connect directly to LDAP through the database server, the parameters for the registerConnectionFactory method include a JDBC connection (to a user having AQ_ADMINISTRATOR_ROLE), the name of the Queue/Topic ConnectionFactory, hostname, database SID, port number, JDBC driver (thin or oci8) and factory type (queue or topic).

After the Connection Factory objects have been registered in LDAP by a JMS administrator, they can be looked up by using JNDI

Example

Lets say the JMS administrator wants to register a order entry queue connection factory, oe_queue_factory. In LDAP, it can be registered as follows:

public static void register_Factory_in_LDAP() throws Exception
{
    Hashtable env = new Hashtable(5, 0.75f);
    env.put(Context.INITIAL_CONTEXT_FACTORY, AQjmsConstants.INIT_CTX_FACTORY);

    // aqldapserv is your LDAP host and 389 is your port
    env.put(Context.PROVIDER_URL, "ldap://aqldapserv:389); 
    
    // now authentication info
    // username/password scheme, user is OE, password is OE
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=oe,cn=users,cn=acme,cn=com");
    env.put(Context.SECURITY_CREDENTIALS, "oe");

       /* register queue connection factory for database "aqdb", host "sun-123", 
       port 5521,  driver "thin" */
       AQjmsFactory.registerConnectionFactory(env, "oe_queue_factory", "sun-123", 
                                          "aqdb", 5521, "thin", "queue");
   }

After order entry, queue connection factory oe_queue_factory has been registered in LDAP; it can be looked up as follows:

public static void get_Factory_from_LDAP() throws Exception
{
    Hashtable env = new Hashtable(5, 0.75f);
    env.put(Context.INITIAL_CONTEXT_FACTORY, AQjmsConstants.INIT_CTX_FACTORY);

    // aqldapserv is your LDAP host and 389 is your port
    env.put(Context.PROVIDER_URL, "ldap://aqldapserv:389); 
    
    // now authentication info
    // username/password scheme, user is OE, password is OE
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=oe,cn=users,cn=acme,cn=com");
    env.put(Context.SECURITY_CREDENTIALS, "oe");

    DirContext inictx = new InitialDirContext(env);
    // initialize context with the distinguished name of the database server
    inictx=(DirContext)inictx.lookup("cn=db1,cn=OracleContext,cn=acme,cn=com");

    //go to the connection factory holder cn=OraclDBConnections
    DirContext connctx = (DirContext)inictx.lookup("cn=OracleDBConnections");

    // get connection factory "oe_queue_factory"
    QueueConnectionFactory qc_fact =          
                  (QueueConnectionFactory)connctx.lookup("cn=oe_queue_factory");
}

Connection

A JMS Connection is a client's active connection to its JMS provider. A Connection performs several critical services:

A JMS Connection to the database can be created by invoking createQueueConnection() or createTopicConnection() and passing the parameters username and password on the QueueConnectionFactory and TopicConnectionFactory object respectively.

Connection Setup

A JMS client typically creates a Connection, Session and a number of MessageProducers and MessageConsumers. In the current version only one open session for each connection is allowed, except in the following cases:

When a Connection is created it is in stopped mode. In this state no messages can be delivered to it. It is typical to leave the Connection in stopped mode until setup is complete. At that point the Connection's start() method is called and messages begin arriving at the Connection's consumers. This setup convention minimizes any client confusion that may result from asynchronous message delivery while the client is still in the process of setup.

It is possible to start a Connection and to perform setup subsequently. Clients that do this must be prepared to handle asynchronous message delivery while they are still in the process of setting up. A MessageProducer can send messages while a Connection is stopped.

Some of the methods that are supported on the Connection object are

Session

A Connection is a factory for Sessions that use its underlying connection to a JMS provider for producing and consuming messages. A JMS Session is a single threaded context for producing and consuming messages. Although it may allocate provider resources outside the Java virtual machine, it is considered a light-weight JMS object.

A Session serves several purposes:

When you use the OCI JDBC driver, you can create multiple sessions for each connection. When you use other JDBC drivers, only one session can be created from one connection.

Because a provider may allocate some resources on behalf of a session outside the JVM, clients should close them when they are not needed. Relying on garbage collection to eventually reclaim these resources may not be timely enough. The same is true for the MessageProducers and MessageConsumers created by a session.

Methods on the Session object include:

The following are some of the extensions to JMS made by Oracle. The Session object has to be cast to AQjmsSession to use any of the extensions.

The following code illustrates how some of the preceding calls are used.

Example Code

public static void bol_example(String ora_sid, String host, int port,
                               String driver)
{
 
 QueueConnectionFactory    qc_fact   = null;
 QueueConnection           q_conn    = null;
 QueueSession              q_sess    = null;
 AQQueueTableProperty      qt_prop   = null;
 AQQueueTable              q_table   = null;
 AQjmsDestinationProperty  dest_prop = null;  
 Queue                     queue     = null;
 BytesMessage              bytes_msg = null;

 try
 {
   /* get queue connection factory */
   qc_fact = AQjmsFactory.getQueueConnectionFactory(host, ora_sid,
                      port, driver);

   /* create queue connection */
   q_conn = qc_fact.createQueueConnection("boluser", "boluser");   

   /* create queue session */
   q_sess = q_conn.createQueueSession(true, Session.CLIENT_ACKNOWLEDGE);

   /* start the queue connection */
   q_conn.start();

   qt_prop = new AQQueueTableProperty("SYS.AQ$_JMS_BYTES_MESSAGE");
  
   /* create a queue table */
   q_table = ((AQjmsSession)q_sess).createQueueTable("boluser",
                                                     "bol_ship_queue_table",
                                                     qt_prop);

   dest_prop = new AQjmsDestinationProperty();

   /* create a queue */
   queue = ((AQjmsSession)q_sess).createQueue(q_table, "bol_ship_queue",
                                             dest_prop);

   /* start the queue */
   ((AQjmsDestination)queue).start(q_sess, true, true);

   /* create a bytes message */
   bytes_msg = q_sess.createBytesMessage();

   /* close session */
   q_sess.close();

   /* close connection */
   q_conn.close();
 }
 catch (Exception ex)
 {
   System.out.println("Exception: " + ex);
 }
}

JMS Destinations - Queue and Topic

A Destination is an object a client uses to specify the destination where it sends messages, and the source from which it receives messages.

There are two types of destination objects - Queue and Topic. In AQ, these map to a <schema>.<queue> at a specific database. Queue maps to a single-consumer queue in AQ and Topic maps to multiconsumer queue in AQ.

Destination objects can be obtained in one of the following ways:

  1. Using domain specific methods in the JMS Session
  2. Java Naming and Directory Interface (JNDI) Lookup from a LDAP directory server

Using a JMS Session to Obtain Destination Objects

Destination objects are created from a Session object using domain specific session methods.

Example Code

In the BooksOnline application, new orders are to be sent to the neworders_queue in OE schema. After creating a JMS connection and session, we can get a handle to the queue as follows

public Queue get_queue_example(QueueSession jms_session)
{
   QueueSender   sender;
   Queue         queue = null;

   try
   {
  /* get a handle to the OE.oe_new_orders queue */   
       queue = ((AQjmsSession) jms_session).getQueue("OE", "OE_neworders_que");
   }
   catch (JMSException ex){
      System.out.println("Exception: " + ex); }
      return queue;
}   
      

Using JNDI to Look Up Destination Objects

As described in "Connection Factory", the database can be configured to register schema objects with an LDAP server. If a database has been configured to use LDAP and the GLOBAL_TOPIC_ENABLED parameter has been set to TRUE, then all JMS queues and topics are automatically registered with the LDAP server when they are created.

The administrator can also create aliases to the queues and topics registered in LDAP using the DBMS_AQAQDM.add_alias_to_ldap PL/SQL procedure.

Queues and topics that are registered in LDAP can be looked up through JNDI using the queue/topic name or one of their aliases.

Example Code

Lets say we have a new orders queue OE.OE_neworders_que stored in LDAP, it can be looked up as follows:

public static void get_Factory_from_LDAP() throws Exception
{
    Hashtable env = new Hashtable(5, 0.75f);
    env.put(Context.INITIAL_CONTEXT_FACTORY, AQjmsConstants.INIT_CTX_FACTORY);

    // aqldapserv is your LDAP host and 389 is your port
    env.put(Context.PROVIDER_URL, "ldap://aqldapserv:389); 
    
    // now authentication info
    // username/password scheme, user is OE, password is OE
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=oe,cn=users,cn=acme,cn=com");
    env.put(Context.SECURITY_CREDENTIALS, "oe");

    DirContext inictx = new InitialDirContext(env);
    // initialize context with the distinguished name of the database server
    inictx=(DirContext)inictx.lookup("cn=db1,cn=OracleContext,cn=acme,cn=com");

   // go to the destination holder
    DirContext destctx = (DirContext)inictx.lookup("cn=OracleDBQueues");
    
    // get the destination OE.OE_new_orders queue
    Queue myqueue = (Queue)destctx.lookup("cn=OE.OE_new_orders_que");

}

Methods on the Destination Object include:

Example Code

public static void setup_example(TopicSession t_sess)
{
  AQQueueTableProperty     qt_prop   = null;
  AQQueueTable             q_table   = null;
  AQjmsDestinationProperty dest_prop = null; 
  Topic                    topic     = null;
  TopicConnection          t_conn    = null;

  try
  {
    qt_prop = new AQQueueTableProperty("SYS.AQ$_JMS_BYTES_MESSAGE");
 /* create a queue table */
    q_table = ((AQjmsSession)t_sess).createQueueTable("boluser",
                           "bol_ship_queue_table",
                           qt_prop);
    dest_prop = new AQjmsDestinationProperty();
 /* create a topic */
    topic = ((AQjmsSession)t_sess).createTopic(q_table, "bol_ship_queue",
                        dest_prop);
  /* start the topic */
    ((AQjmsDestination)topic).start(t_sess, true, true);
     
 /* schedule propagation from topic "boluser" to the destination 
 dblink "dba" */
    ((AQjmsDestination)topic).schedulePropagation(t_sess, "dba", null,
                   null, null, null);
  /* 
  some processing done here
 */
 /* Unschedule propagation */
    ((AQjmsDestination)topic).unschedulePropagation(t_sess, "dba");
 /* stop the topic */
    ((AQjmsDestination)topic).stop(t_sess, true, true, true);
 /* drop topic */
    ((AQjmsDestination)topic).drop(t_sess);
 /* drop queue table */
    q_table.drop(true);
 /* close session */
    t_sess.close();
 /* close connection */
    t_conn.close();
  }
  catch(Exception ex)
  {
    System.out.println("Exception: " + ex);
  }
}

System-Level Access Control in JMS

Oracle8i supports system-level access control for all queuing operations. This feature allows an application designer or DBA to create users as queue administrators. A queue/topic administrator can invoke all JMS interface (both administration and operation) on any queue in the database. This simplifies the administrative work since all administrative scripts for the queues in a database can be managed under one schema. For more information, see "Oracle Enterprise Manager Support" .

Example Scenario and Code

In the BooksOnLine (BOL) application, the DBA creates BOLADM, the BooksOnLine Administrator account, as the queue administrator of the database. This allows BOLADM to create, drop, manage, and monitor any queues in the database.If you decide to create PL/SQL packages in the BOLADM schema that can be used by any applications to enqueue or dequeue, then you should also grant BOLADM the ENQUEUE_ANY and DEQUEUE_ANY system privilege.

CREATE USER BOLADM IDENTIFIED BY BOLADM; GRANT CONNECT, RESOURCE, aq_
administrator_role TO BOLADM; 
((AQjmsSession)t_sess).grantSystemPrivilege("ENQUEUE_ANY", "BOLADM", false);
((AQjmsSession)t_sess).grantSystemPrivilege("DEQUEUE_ANY", "BOLADM", false)
;where t_sess is the session object.

In the application, AQ propagators populate messages from the OE (Order Entry) schema to WS (Western Sales), ES (Eastern Sales) and OS (Worldwide Sales) schemas. The WS, ES and OS schemas in turn populate messages to CB (Customer Billing) and CS (Customer Service) schemas. Hence the OE, WS, ES and OS schemas all host queues that serve as the source queues for the propagators.

When messages arrive at the destination queues, sessions based on the source queue schema name are used for enqueuing the newly arrived messages into the destination queues. This means that you need to grant schemas of the source queues enqueue privileges to the destination queues.

To simplify administration, all schemas that host a source queue in the BooksOnLine application are granted the ENQUEUE_ANY system privilege.

((AQjmsSession)t_sess).grantSystemPrivilege("ENQUEUE_ANY", "OE", false);
((AQjmsSession)t_sess).grantSystemPrivilege("ENQUEUE_ANY", "WS", false);
((AQjmsSession)t_sess).grantSystemPrivilege("ENQUEUE_ANY", "ES", false);
((AQjmsSession)t_sess).grantSystemPrivilege("ENQUEUE_ANY", "OS", false);
where t_sess is the session object

To propagate to a remote destination queue, the login user (specified in the database link in the address field of the agent structure) should either be granted the 'ENQUEUE ANY' privilege, or be granted the rights to enqueue to the destination queue. However, you do not need to grant any explicit privileges if the login user in the database link also owns the queue tables at the destination.

Destination-Level Access Control in JMS

Oracle8i supports queue/topic level access control for enqueue and dequeue operations. This feature allows the application designer to protect queues/topics created in one schema from applications running in other schemas. You need to grant only minimal access privileges to the applications that run outside the queue/topic's schema. The supported access privileges on a queue/topic are ENQUEUE, DEQUEUE and ALL. For more information see "Oracle Enterprise Manager Support" in Chapter 4, "Managing AQ".

Example Scenario and Code

The BooksOnLine application processes customer billings in its CB and CBADM schemas. CB (Customer Billing) schema hosts the customer billing application, and the CBADM schema hosts all related billing data stored as queue tables. To protect the billing data, the billing application and the billing data reside in different schemas. The billing application is allowed only to dequeue messages from CBADM_shippedorders_topic, the shipped order topic. It processes the messages, and then enqueues new messages into CBADM_billedorders_topic, the billed order topic.

To protect the queues from other illegal operations from the application, the following two grant calls are made:

/* Grant dequeue privilege on the shipped orders queue to the Customer 
 Billing application. The CB application retrieves orders that are shipped 
 but not billed from the shipped orders queue. */  

((AQjmsDestination)cbadm_shippedorders_topic).grantTopicPrivilege(t_sess, 
"DEQUEUE", "CB", false);
where t_sess is the session

/* Grant enqueue privilege on the billed orders queue to Customer Billing 
 application.The CB application is allowed to put billed orders into this 
 queue after processing the orders. */ 

((AQjmsDestination)cbadm_billedorders_topic).grantTopicPrivilege(t_sess, 
"ENQUEUE", "CB", false);

Retention and Message History in JMS

AQ allows users retain messages in the queue table. This means that SQL can then be used to query these message for analysis. Messages are often related to each other. For example, if a message is produced as a result of the consumption of another message, the two are related. As the application designer, you may want to keep track of such relationships. Along with retention and message identifiers, AQ lets you automatically create message journals, also called tracking journals or event journals. Taken together -- retention, message identifiers and SQL queries -- make it possible to build powerful message warehouses.

Example Scenario and Code

Let us suppose that the shipping application needs to determine the average processing times of orders. This includes the time the order has to wait in the backed_order topic. Specifying the retention as TRUE for the shipping queues and specifying the order number in the correlation field of the message, SQL queries can be written to determine the wait time for orders in the shipping application.

For simplicity, we will only analyze orders that have already been processed. The processing time for an order in the shipping application is the difference between the enqueue time in the WS_bookedorders_topic and the enqueue time in the WS_shipped_orders_topic.

SELECT  SUM(SO.enq_time - BO.enq_time) / count (*) AVG_PRCS_TIME 
   FROM WS.AQ$WS_orders_pr_mqtab BO , WS.AQ$WS_orders_mqtab SO  
   WHERE SO.msg_state = 'PROCESSED' and BO.msg_state = 'PROCESSED' 
   AND SO.corr_id = BO.corr_id and SO.queue = 'WS_shippedorders_topic'; 
 
/* Average waiting time in the backed order queue: */ 
SELECT SUM(BACK.deq_time - BACK.enq_time)/count (*) AVG_BACK_TIME 
   FROM WS.AQ$WS_orders_mqtab BACK  
   WHERE BACK.msg_state = 'PROCESSED' AND BACK.queue = 'WS_backorders_topic'; 

Supporting Oracle Real Application Clusters in JMS

Oracle Real Application Clusters can be used to improve AQ performance by allowing different queues to be managed by different instances. You do this by specifying different instance affinities (preferences) for the queue tables that store the queues. This allows queue operations (enqueue/dequeue) or topic operations (publish/subscribe) on different queues or topics to occur in parallel.

The AQ queue monitor process continuously monitors the instance affinities of the queue tables. The queue monitor assigns ownership of a queue table to the specified primary instance if it is available, failing which it assigns it to the specified secondary instance.

If the owner instance of a queue table terminates, the queue monitor changes ownership to a suitable instance such as the secondary instance.

AQ propagation is able to make use of Real Application Clusters, although it is transparent to the user. The affinities for jobs submitted on behalf of the propagation schedules are set to the same values as that of the affinities of the respective queue tables. Thus, a job_queue_process associated with the owner instance of a queue table will be handling the propagation from queues stored in that queue table thereby minimizing pinging. Additional discussion on this topic can be found under AQ propagation scheduling (see "Scheduling a Queue Propagation"in Chapter 9, "Administrative Interface"and Oracle9i Real Application Clusters Setup and Configuration.)

Example Scenario and Code

In the BooksOnLine example, operations on the OE_neworders_que and booked_order_topic at the order entry (OE) site can be made faster if the two topics are associated with different instances. This is done by creating the topics in different queue tables and specifying different affinities for the queue tables in the CreateQueueTable() command.

In the example, the queue table OE_orders_sqtab stores queue OE_neworders_que and the primary and secondary are instances 1 and 2 respectively. For queue table OE_orders_mqtab stores queue booked_order_topic and the primary and secondary are instances 2 and 1 respectively. The objective is to let instances 1 & 2 manage the two queues in parallel. By default, only one instance is available. In this case the owner instances of both queue tables will be set to instance1. However, if Oracle Real Application Clusters are set up correctly and both instances 1 and 2 are available, then queue table OE_orders_sqtab will be owned by instance 1 and the other queue table will be owned by instance 2. The primary and secondary instance specification of a queue table can be changed dynamically using the alter_queue_table() command as shown in the example that follows. Information about the primary, secondary and owner instance of a queue table can be obtained by querying the view USER_QUEUE_TABLES. See "Selecting Queue Tables in User Schema" in Chapter 10, "Administrative Interface: Views".

/* Create queue tables, topics for OE */

/* createing a queue table to hold queues */
qt_prop = new AQQueueTableProperty("SYS.AQ$_JMS_OBJECT_MESSAGE");
qt_prop.setPrimaryInstance(1);
qt_prop.setSecondaryInstance(2);
q_table = createQueueTable("OE", "OE_orders_sqtab", qt_prop);

/* creating a queue table to hold topics */
qt1_prop = new AQQueueTableProperty("SYS.AQ$_JMS_OBJECT_MESSAGE");
qt1_prop.setMultiConsumer(TRUE);
qt1_prop.setPrimaryInstance(2);
qt1_prop.setSecondaryInstance(1);
q_table1 = createQueueTable("OE", "OE_orders_mqtab", qt1_prop);

dest_prop = new AQjmsDestinationProperty();
queue = ((AQjmsSession)q_sess).createQueue(q_table. "OE_neworders_que", 
                                           dest_prop);

dest_prop1 = new AQjmsDestinationProperty();
topic = ((AQjmsSession)q_sess).createTopic(q_table1, "OE_bookedorders_topic", 
                                           dest_prop1);

  
/* Check instance affinity of OE queue tables from AQ administrative view: */ 
SELECT queue_table, primary_instance, secondary_instance, owner_instance 
FROM user_queue_tables; 

/* Alter Instance Affinity of OE queue tables */
q_table.alter("OE_orders_sqtab", 2, 1);
q_table1.alter("OE_orders_mqtabl", 1, 2);

Supporting Statistics Views in JMS

Each instance keeps its own AQ statistics information in its own System Global Area (SGA), and does not have knowledge of the statistics gathered by other instances. Then, when a GV$AQ view is queried by an instance, all other instances funnel their AQ statistics information to the instance issuing the query.

Example Scenario and Code

The gv$view can be queried at any time to see the number of messages in waiting, ready or expired state. The view also displays the average number of seconds messages have been waiting to be processed. The order processing application can use this to dynamically tune the number of order-processing processes. See Chapter , "Selecting the Number of Messages in Different States for the Whole Database" in Chapter 10, "Administrative Interface: Views".

CONNECT oe/oe 
 
/* Count the number as messages and the average time for which the messages 
   have been waiting: */ 
SELECT READY, AVERAGE_WAIT 
FROM gv$aq Stats, user_queues Qs 
WHERE Stats.qid = Qs.qid and Qs.Name = 'OE_neworders_que'; 

Structured Payload/Message Types in JMS

JMS Messages are composed of the following parts:

Message Headers

You can use a header-only JMS message. A message body is not required. The message header contains the following fields:

Message Properties

Properties are a mechanism to add optional header fields to a message. Properties allow a client, using message selectors, to have a JMS provider select messages on its behalf using application-specific criteria. Property names are Strings and values can be: boolean, byte, short, int, long, float, double, and string.

JMS-defined properties begin with "JMSX".

Oracle JMS specific properties begin with JMS_Oracle. The following properties are Oracle-specific:

A client can add additional header fields to a message by defining properties. These properties can then be used in message selectors to select specific messages.

JMS properties or header fields are set either explicitly by the client or automatically by the JMS provider (these are generally read-only). Some JMS properties are set using the parameters specified send and receive operations.

Table 12-1 Message Header Fields
Message Header Field  Type Set by Use

JMSDestination

Destination

Set by JMS after Send Method has completed

The destination to which the message is sent

JMSDeliveryMode

int

Set by JMS after Send Method has completed

The delivery mode -PERSISTENT

JMSExpiration

long

Set by JMS after Send Method has completed

The expiration time can be specified for a Message Producer or can be explicitly specified during each send or publish

JMSPriority

int

Set by JMS after Send Method has completed

Message's priority can be specified for a Message Producer or can be explicitly specified during each send or publish

JMSMessageID

String

Set by JMS after Send Method has completed

A value that uniquely identifies each message sent by the provider

JMSTimeStamp

long

Set by JMS after Send Method has completed

The time a message is handed to a provider to be sent

JMSCorrelationID

String

Set by JMS client

A field that can be used to link one message with another

JMSReplyTo

Destination

Set by JMS client

A destination set by the client, where a reply to the message should be sent. Should be specified as AQjsAgent, javax.jms.Queue, or javax.jms.Topic types

JMSType

String

Set by JMS client

Message type identifier

JMSRedelivered

boolean

Set by JMS provider

The message probably was delivered earlier but the client did not acknowledge it at that time

Table 12-2 JMS Defined Message Properties
JMS Defined Message Property Type Set by Use

JMSXUserID

String

Set by JMS after Send Method has completed

The identity of the user sending the message

JMSAppID

String

Set by JMS after Send Method has completed

The identity of the application sending the message

JMSDeliveryCount

int

Set by JMS after Receive Method has completed

The number of message delivery attempts; the first is 1, second is 2,...

JMSXGroupID

String

Set by JMS client

The identity of the message group the message is a part of

JMSXGroupSeq

int

Set by JMS client

The sequence number of the message within the group first message is 1, second message is 2...

JMSXRcvTimeStamp

String

Set by JMS after Receive Method has completed

The time that JMS delivered the message to the consumer

JMSXState

int

Set by JMS Provider

Message state set by provider

Table 12-3 Oracle Defined Message Properties
Header Field/Property Type Set by Use

JMS_OracleExcpQ

String

Set by JMS Client

Specifies the name of the exception queue

JMS_OracleDelay

int

Set by JMS Client

Specifies the time (seconds) after which the message should become available to the consumers

JMS_OracleOriginalMessageID

String

Set by JMS Provider

Specifies the message id of the message in source when the messages are propagated from one destination to another

Message Body

JMS provides five forms of message body:

The AQ$_JMS_MESSAGE Type

This type can store JMS messages of all the JMS-specified message types: JMSStream, JMSBytes, JMSMap, JMSText, and JMSObject. You can create a queue table of AQ$_JMS_MESSAGE type, but use any message type.

Stream Message

A StreamMessage is used to send a stream of Java primitives. It is filled and read sequentially. It inherits from Message and adds a stream message body. Its methods are based largely on those found in java.io.DataInputStream and java.io.DataOutputStream.

The primitive types can be read or written explicitly using methods for each type. They may also be read or written generically as objects. To use Stream Messages, create the queue table with the SYS.AQ$_JMS_STREAM_MESSAGE or AQ$_JMS_MESSAGE payload types.

Stream messages support the following conversion table. A value written as the row type can be read as the column type.

Table 12-4 Stream Message Conversion
boolean byte short char int long float double String byte[]

boolean

X

-

-

-

-

-

-

-

X

-

byte

-

X

X

-

X

X

-

-

X

-

short

-

-

X

-

X

X

-

-

X

-

char

-

-

-

X

-

-

-

-

X

-

int

-

-

-

-

X

X

-

-

X

-

long

-

-

-

-

-

X

-

-

X

-

float

-

-

-

-

-

-

X

X

X

-

double

-

-

-

-

-

-

-

X

X

-

String

X

X

X

X

X

X

X

X

X

-

byte[]

-

-

-

-

-

-

-

-

-

X

Bytes Message

A BytesMessage is used to send a message containing a stream of uninterpreted bytes. It inherits Message and adds a bytes message body. The receiver of the message supplies the interpretation of the bytes. Its methods are based largely on those found in java.io.DataInputStream and java.io.DataOutputStream.

This message type is for client encoding of existing message formats. If possible, one of the other self-defining message types should be used instead.

The primitive types can be written explicitly using methods for each type. They may also be written generically as objects. To use Bytes Messages, create the queue table with SYS.AQ$_JMS_BYTES_MESSAGE or AQ$_JMS_MESSAGE payload types.

Map Message

A MapMessage is used to send a set of name-value pairs where names are Strings and values are Java primitive types. The entries can be accessed sequentially or randomly by name. The order of the entries is undefined. It inherits from Message and adds a map message body. The primitive types can be read or written explicitly using methods for each type. They may also be read or written generically as objects.

To use Map Messages, create the queue table with the SYS.AQ$_JMS_MAP_MESSAGE or AQ$_JMS_MESSAGE payload types. Map messages support the following conversion table. A value written as the row type can be read as the column type.

Table 12-5 Map Message Conversion
boolean byte short char int long float double String byte[]

boolean

X

-

-

-

-

-

-

-

X

-

byte

-

X

X

-

X

X

-

-

X

-

short

-

-

X

-

X

X

-

-

X

-

char

-

-

-

X

-

-

-

-

X

-

int

-

-

-

-

X

X

-

-

X

-

long

-

-

-

-

-

X

-

-

X

-

float

-

-

-

-

-

-

X

X

X

-

double

-

-

-

-

-

-

-

X

X

-

String

X

X

X

X

X

X

X

X

X

-

byte[]

-

-

-

-

-

-

-

-

-

X

Text Message

A TextMessage is used to send a message containing a java.lang.StringBuffer. It inherits from Message and adds a text message body. The text information can be read or written using methods getText() and setText(...). To use Text Messages, create the queue table with the SYS.AQ$_JMS_TEXT_MESSAGE or AQ$_JMS_MESSAGE payload types.

Object Message

An ObjectMessage is used to send a message that contains a serializable Java object. It inherits from Message and adds a body containing a single Java reference. Only serializable Java objects can be used. If a collection of Java objects must be sent, one of the collection classes provided in JDK 1.2 can be used. The objects can be read or written using the methods getObject() and setObject(...).To use Object Messages, create the queue table with the SYS.AQ$_JMS_OBJECT_MESSAGE or AQ$_JMS_MESSAGE payload types.

Example Code

public void enqueue_new_orders(QueueSession jms_session, BolOrder new_order)
{
   QueueSender   sender;
   Queue         queue;
   ObjectMessage obj_message;   

   try
   {
       /* get a handle to the new_orders queue */   
       queue = ((AQjmsSession) jms_session).getQueue("OE", "OE_neworders_que");
       sender = jms_session.createSender(queue);
       obj_message = jms_session.createObjectMessage();
       obj_message.setJMSCorrelationID("RUSH");   
       obj_message.setObject(new_order);
       jms_session.commit();   
    }
    catch (JMSException ex)
    {
      System.out.println("Exception: " + ex); 
    }
   
}   

AdtMessage

An AdtMessage is used to send a message that contains a Java object that maps to an Oracle Object type. These objects inherit from Message and add a body containing a Java object that implements the CustomDatum or ORAData interface.

See Also:

Oracle9i Java Developer's Guide for information about the CustomDatum and ORAData interfaces

To use AdtMessage, create the queue table with payload type as the Oracle Object Type. The AdtMessage payload can be read and written using the getAdtPayload and setAdtPayload methods.

You can also use an AdtMessage to send messages to queues of type SYS.XMLType. You must use the oracle.xdb.XMLType class to create the message.

Using Message Properties with Different Message Types

Payload Used by JMS Examples

/*
 *  BooksOrder - payload for BooksOnline example
 *
 */

import java.lang.*;
import java.io.*;
import java.util.*;

public class BolOrder implements Serializable
{

  int             orderno;
  String          status;
  String          type;
  String          region;
  BolCustomer     customer;
  String          paymentmethod;
  BolOrderItem[]  itemlist;
  String          ccnumber;
  Date            orderdate;

  public BolOrder(int orderno, BolCustomer customer)
  {
    this.customer   = customer;
    this.orderno    = orderno;
  }

  public int getOrderNo()
  {
    return orderno;
  }

  public String getStatus()
  {
    return status;
  }


  public void setStatus(String new_status)
  {
    status = new_status;
  }


  public String getRegion()
  {
    return region;
  }


  public void setRegion(String region)
  {
    this.region = region;
  }

  public BolCustomer getCustomer()
  {
    return customer;
  }


  public String getPaymentmethod()
  {
    return paymentmethod;
  }

  public void setPaymentmethod(String paymentmethod)
  {
    this.paymentmethod = paymentmethod;
  }

  public BolOrderItem[] getItemList()
  {
    return itemlist;
  }

  public void setItemList(BolOrderItem[] itemlist)
  {
    this.itemlist = itemlist;
  }

  public String getCCnumber()
  {
    return ccnumber;
  }

  public void setCCnumber(String ccnumber)
  {
    this.ccnumber = ccnumber;
  }

  public Date getOrderDate()
  {
    return orderdate;
  }

  public void setOrderDate(Date orderdate)
  {
    this.orderdate = orderdate;
  }

}

/*
 *  BolOrderItem - order item type  for BooksOnline example
 *
 */

import java.lang.*;
import java.io.*;
import java.util.*;

public class BolOrderItem implements Serializable
{

  BolBook     item;
  int         quantity;

  public BolOrderItem(BolBook book, int quantity)
  {  
    item          = book;
    this.quantity = quantity;

  }

  public BolBook getItem()
  {
    return item;
  }

  public int getQuantity()
  {
    return quantity;
  }
}


/*
 *  BolBook - book type  for BooksOnline example
 *
 */

import java.lang.*;
import java.io.*;
import java.util.*;

public class BolBook implements Serializable
{

  String    title;
  String    authors;
  String    isbn;
  float     price;
 
  public BolBook(String title)
  {  
    this.title   = title;
  }

  public BolBook(String title, String authors, String isbn, float price)
  {  
    this.title   = title;
    this.authors = authors;
    this.isbn    = isbn;
    this.price   = price;

  }

  public String getISBN()
  {
    return isbn;
  }

  public String getTitle()
  {
    return title;
  }

  public String getAuthors()
  {
    return authors;
  }

  public float getPrice()
  {
    return price;
  }

}
/*
 *  BolCustomer - customer type  for BooksOnline example
 *
 */

import java.lang.*;
import java.io.*;
import java.util.*;

public class BolCustomer implements Serializable
{

  int          custno;
  String       custid;
  String       name;
  String       street;
  String       city;
  String       state;
  int          zip;
  String       country;

  public BolCustomer(int custno, String name)
  {

    this.custno  = custno;
    this.name    = name;
  }

  public BolCustomer(int custno, String custid, String name, String  street,
           String city, String state, int zip, String country)
  {

    this.custno  = custno;
    this.custid  = custid;
    this.name    = name;
    this.street  = street;
    this.city    = city;
    this.state   = state;
    this.zip     = zip;
    this.country = country;

  }

  public int getCustomerNo()
  {
    return custno;
  }

  public String getCustomerId()
  {
    return custid;
  }

  public String getName()
  {
    return name;
  }

  public String getStreet()
  {
    return street;
  }

  public String getCity()
  {
    return city;
  }

  public String getState()
  {
    return state;
  }

  public int getZipcode()
  {
    return zip;
  }

  public String getCountry()
  {
    return country;
  }
}

JMS Point-to-Point Model Features

Queues

In the point-to-point model, clients exchange messages using queues - from one point to another. These queues are used by message producers and consumers to send and receive messages.

An administrator creates single-consumer queues by means of the createQueue method in AQjmsSession. A client may obtain a handle to a previously created queue using the getQueue method on AQjmsSession.

These queues are described as single-consumer queues because a message can be consumed by only a single consumer. Put another way: a message can be consumed exactly once. This raises the question: What happens when there are multiple processes or operating system threads concurrently dequeuing from the same queue? Since a locked message cannot be dequeued by a process other than the one that has created the lock, each process will dequeue the first unlocked message at the head of the queue.

Before using a queue, the queue needs to be enabled for enqueue/dequeue using start call in AQjmsDestination.

After processing, the message is removed if the retention time of the queue is 0, or is retained for a specified retention time. As long as the message is retained, it can be either

Queue Sender

A client uses a QueueSender to send messages to