The QBorrower and QLender Application

To illustrate how point-to-point messaging works, we will use a simple decoupled request/reply example where a QBorrower class makes a simple mortgage loan request to a QLender class using point-to-point messaging. The QBorrower class sends the loan request to the QLender class using a LoanRequest queue, and based on certain business rules, the QLender class sends a response back to the QBorrower class using a LoanResponseQ queue indicating whether the loan request was approved or denied. Since the QBorrower is interested in finding out right away whether the loan was approved or not, once the loan request is sent, the QBorrower class will block and wait for a response from the QLender class before proceeding. This simple example models a typical messaging request/reply scenario.

Configuring and Running the Application

Before looking at the code, let’s look at how the application works. As with the Chat application, the QBorrower class and QLender class both include a main() method so they can be run as a standalone Java application. To keep the code vendor-agnostic, both classes need the connection factory name and queue names when starting the application. The QLender class is executed from the command line as follows:

java ch04.p2p.QLender ConnectionFactory RequestQueue

where ConnectionFactory is the name of the queue connection factory defined in your JMS provider and RequestQueue is the name of the queue that the QLender class should be listening on to receive loan requests. As you’ll see later in this chapter, the QBorrower sends the destination for the response message in the JMSReplyTo header property, which is why you do not need to specify it when starting the QLender class.

The QBorrower class can be executed in the same manner in a separate command window:

java ch04.p2p.QBorrower ConnectionFactory RequestQueue ReplyQueue            

where ConnectionFactory is the name of the queue connection factory defined in your JMS provider, RequestQueue is the name of the queue that the QBorrower class should send loan requests to, and ReplyQueue is the name of the queue that the QBorrower class should use to receive the results from the QLender class.

You will also need to define a jndi.properties file in your classpath that contains the JNDI connection information for the JMS provider. The jndi.properties file contains the initial context factory class, provider URL, username, and password needed to connect to the JMS server. Each vendor will have a different context factory class and URL name for connecting to the server. You will need to consult the documentation of your specific JMS provider or Java EE container to obtain these values. We have included the steps for configuring ActiveMQ to run the examples in this chapter in Appendix D.

The QBorrower and QLender classes both require the queue connection factory name and queue names to run. We have chosen to name the connection factory QueueCF, and the loan request and loan response queues LoanRequestQ and LoanResponseQ, respectively. These JNDI resources are typically configured in the JMS provider XML configuration files or configuration screens. You will need to consult your JMS provider documentation on how to configure these resources (please refer to Appendix D for the specific configuration settings for ActiveMQ used to run the examples in this chapter).

You can run the QBorrower and QLender classes by entering the following two commands in separate command windows:

java ch04.p2p.QLender QueueCF LoanRequestQ

java ch04.p2p.QBorrower QueueCF LoanRequestQ LoanResponseQ

When the QBorrower class starts, you will be prompted to enter a salary amount and the requested loan amount. When you press enter, the QBorrower class will send the salary and loan amount to the QLender class via the LoanRequestQ queue, wait for the response on the LoanResponseQ queue, and display whether the loan was approved or denied:

QBorrower Application Started
Press enter to quit application
Enter: Salary, Loan_Amount
e.g. 50000, 120000

> 80000, 200000
Loan request was Accepted!

> 50000, 300000
Loan request was Declined
> 

Here’s what happened. The QBorrower sent the salary ($80,000) and the loan amount ($200,000) to the LoanRequestQ queue, then blocked and waited for a response from the QLender class. The QLender class received the request on the LoanRequestQ queue, applied the simple business logic based on the salary to loan ratio, and sent back the response on the LoanResponseQ queue. The message was then received by the QBorrower class, and the contents of the return message displayed on the console. This interaction is illustrated in Figure 4-3.

Producers and consumers in the loan example

Figure 4-3. Producers and consumers in the loan example

The rest of this chapter examines the source code for the QBorrower and QLender classes, and covers several advanced subjects related to the point-to-point messaging model.

The QBorrower Class

The QBorrower class is responsible for sending a loan request message to a queue containing a salary and loan amount. The class is fairly straightforward: the constructor establishes a connection to the JMS provider, creates a QueueSession, and gets the request and response queues using a JNDI lookup. The main method instantiates the QBorrower class and, upon receiving a salary and loan amount from standard input, invokes the sendLoanRequest method to send the message to the queue. Here is the listing for the QBorrower class in its entirety. We will be examining the JMS aspects of this class in detail after the full listing:

package ch04.p2p;

import java.io.*;
import java.util.StringTokenizer;
import javax.jms.*;
import javax.naming.*;

public class QBorrower {

   private QueueConnection qConnect = null;    
   private QueueSession qSession = null;
   private Queue responseQ = null;
   private Queue requestQ = null;

   public QBorrower(String queuecf, String requestQueue, 
                   String responseQueue) {    
      try {
         // Connect to the provider and get the JMS connection
         Context ctx = new InitialContext();
         QueueConnectionFactory qFactory = (QueueConnectionFactory)
            ctx.lookup(queuecf);
         qConnect = qFactory.createQueueConnection();

         // Create the JMS Session
         qSession = qConnect.createQueueSession(
            false, Session.AUTO_ACKNOWLEDGE);

         // Lookup the request and response queues
         requestQ = (Queue)ctx.lookup(requestQueue);
         responseQ = (Queue)ctx.lookup(responseQueue);

         // Now that setup is complete, start the Connection
         qConnect.start();

      } catch (JMSException jmse) {
         jmse.printStackTrace(); 
         System.exit(1);
      } catch (NamingException jne) {
         jne.printStackTrace(); 
         System.exit(1);
      }
   }

   private void sendLoanRequest(double salary, double loanAmt) {
      try {
         // Create JMS message
         MapMessage msg = qSession.createMapMessage();
         msg.setDouble("Salary", salary);
         msg.setDouble("LoanAmount", loanAmt);
         msg.setJMSReplyTo(responseQ);

         // Create the sender and send the message
         QueueSender qSender = qSession.createSender(requestQ);
         qSender.send(msg);
        
         // Wait to see if the loan request was accepted or declined
         String filter = 
            "JMSCorrelationID = '" + msg.getJMSMessageID() + "'";
         QueueReceiver qReceiver = qSession.createReceiver(responseQ, filter);
         TextMessage tmsg = (TextMessage)qReceiver.receive(30000);
         if (tmsg == null) {
            System.out.println("QLender not responding");
         } else {
            System.out.println("Loan request was " + tmsg.getText());
         }
        
      } catch (JMSException jmse) {
         jmse.printStackTrace(); 
         System.exit(1);
      }
   }

   private void exit() {
      try {
         qConnect.close();
      } catch (JMSException jmse) {
         jmse.printStackTrace();
      }
      System.exit(0);
   }

   public static void main(String argv[]) {
      String queuecf = null;
      String requestq = null;
      String responseq = null;
      if (argv.length == 3) {
         queuecf = argv[0];
         requestq = argv[1];
         responseq = argv[2];
      } else {
         System.out.println("Invalid arguments. Should be: ");
         System.out.println
            ("java QBorrower factory requestQueue responseQueue");
         System.exit(0);
      }
      
      QBorrower borrower = new QBorrower(queuecf, requestq, responseq);
      
      try {
         // Read all standard input and send it as a message
         BufferedReader stdin = new BufferedReader
            (new InputStreamReader(System.in));
         System.out.println ("QBorrower Application Started");
         System.out.println ("Press enter to quit application");
         System.out.println ("Enter: Salary, Loan_Amount");
         System.out.println("\ne.g. 50000, 120000");

         while (true) {
            System.out.print("> ");
            String loanRequest = stdin.readLine();
            if (loanRequest == null || 
                loanRequest.trim().length() <= 0) {
               borrower.exit();
            }
            
            // Parse the deal description
            StringTokenizer st = new StringTokenizer(loanRequest, ",") ;
            double salary = 
               Double.valueOf(st.nextToken().trim()).doubleValue();
            double loanAmt = 
               Double.valueOf(st.nextToken().trim()).doubleValue();

            borrower.sendLoanRequest(salary, loanAmt);
         }
      } catch (IOException ioe) {
         ioe.printStackTrace();
      }
   }
}

The main method of the QBorrower class accepts three arguments from the command line: the JNDI name of the queue connection factory, the JNDI name of the loan request queue, and finally, the JNDI name of the loan response queue where the response from the QLender class will be received. Once the input parameters have been validated, the QBorrower class is instantiated and a loop is started that reads the salary and loan amount into the class from the console:

String loanRequest = stdin.readLine();

The salary and loan amount input data is then parsed, and finally the sendLoanRequest method invoked. The input loop continues until the user presses enter on the console without entering any data:

if (loanRequest == null || 
    loanRequest.trim().length() <= 0) { 
   borrower.exit(); 
}

Now let’s look at the JMS portion of the code in detail, starting with the constructor and ending with the sendLoanRequest method.

JMS Initialization

In the QBorrower class example, all of the JMS initialization logic is handled in the constructor. The first thing the constructor does is establish a connection to the JMS provider by creating an InitialContext:

Context ctx = new InitialContext();

The connection information needed to connect to the JMS provider is specified in the jndi.properties file located in the classpath (see Appendix D for an example). Once we have a JNDI context, we can get the QueueConnectionFactory using the JNDI connection factory name passed into the constructor arguments. The QueueConnectionFactory is then used to create the QueueConnection using a factory method on the QueueConnectionFactory:

QueueConnectionFactory qFactory = 
   (QueueConnectionFactory) ctx.lookup(queuecf);
qConnect = qFactory.createQueueConnection();

Alternatively, you can pass a username and password into the createQueueConnection method as String arguments to perform basic authentication on the connection. A JMSSecurityException will be thrown if the user fails to authenticate:

qConnect = qFactory.createQueueConnection("system", "manager");

At this point a connection is created to the JMS provider. When the QueueConnection is created, the connection is initially in stopped mode. This means you can send messages to the queue, but no message consumers (including the QBorrower class, which is also a message consumer) may receive messages from this connection until it is started.

The QueueConnection object is used to create a JMS Session object (specifically, a QueueSession), which is the working thread and transactional unit of work in JMS. Unlike JDBC, which requires a connection for each transactional unit of work, JMS uses a single connection and multiple Session objects. Typically, applications will create a single JMS Connection on application startup and maintain a pool of Session objects for use whenever a message needs to be produced or consumed.

The QueueSession object is created through a factory object on the QueueConnection object. The QueueConnection variable is declared outside of the constructor in our example so that the connection can be closed in the exit method of the QBorrower class. It is important to close the connection after it is no longer being used to free up resources. Closing the Connection object also closes any open Session objects associated with the connection. The statement in the constructor to create the QueueSession is as follows:

qSession = 
   qConnect.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);

Notice that the createQueueSession method takes two parameters. The first parameter indicates whether the QueueSession is transacted or not. A value of true indicates that the session is transacted, meaning that messages sent to queues during the lifespan of the QueueSession will not be delivered to the receivers until the commit method is invoked on the QueueSession. Likewise, invoking the rollback method on the QueueSession will remove any messages sent during the transacted session. The second parameter indicates the acknowledgment mode. The three possible values are Session.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE, and Session.DUPS_OK_ACKNOWLEDGE. The acknowledgment mode is ignored if the session is transacted. Acknowledgment modes are discussed in more detail in Chapter 7.

The next two lines in the constructor perform a JNDI lookup to the JMS provider to obtain the administered destinations. In our case, the JMS destination is cast to a Queue. The argument supplied to each of the lookup methods is a String value containing the JNDI name of the queues we are using in the class:

requestQ = (Queue)ctx.lookup(requestQueue); 
responseQ = (Queue)ctx.lookup(responseQueue);

The final line of code starts the connection, allowing messages to be received on this connection. It is generally a good idea to perform all of your initialization logic before starting the connection:

qConnect.start();

Interestingly enough, you do not need to start the connection if all you are doing is sending messages. However, it is generally advisable to start the connection to avoid future issues if there is a chance the connection may be shared or request/reply processing added to the sender class.

Another useful thing you can obtain from the JMS Connection is the metadata about the connection. Invoking the getMetaData method on the Connection object gives you a ConnectionMetaData object that provides useful information such as the JMS version, JMS provider name, JMS provider version, and the JMSX property name extensions supported by the JMS provider:

import java.util.Enumeration;
import javax.jms.ConnectionMetaData;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class MetaData {
  public static void main(String[] args) {    
    try {
      Context ctx = new InitialContext();
      QueueConnectionFactory qFactory = (QueueConnectionFactory)
         ctx.lookup("QueueCF");
      QueueConnection qConnect = qFactory.createQueueConnection();
      ConnectionMetaData metadata = qConnect.getMetaData();
      System.out.println("JMS Version:  " + 
                          metadata.getJMSMajorVersion() + "." + 
                          metadata.getJMSMinorVersion());
      System.out.println("JMS Provider: " + 
                          metadata.getJMSProviderName());
      System.out.println("JMSX Properties Supported: ");
      Enumeration e = metadata.getJMSXPropertyNames();
      while (e.hasMoreElements()) {
        System.out.println("   " + e.nextElement());
      }
    } catch (Exception ex) {
      ex.printStackTrace(); 
      System.exit(1);
    }
  }
}

For example, invoking the previous code using the ActiveMQ open source JMS provider will yield the following results:

JMS Version:  1.1
JMS Provider: ActiveMQ
JMSX Properties Supported: 
   JMSXGroupID
   JMSXGroupSeq
   JMSXDeliveryCount
   JMSXProducerTXID

This information can be logged on application startup, indicating the JMS provider and version numbers. It is particularly useful for products or applications that may use multiple providers.

Sending the message and receiving the response

Once the QBorrower class is initialized, the salary and loan amounts are entered through the command line. At this point, the sendLoanRequest method is invoked from the main method to send the loan request to the queue and wait for the response from the QLender class. At the start of this method, we chose to create a MapMessage but we could have used any of the five JMS message types:

MapMessage msg = qSession.createMapMessage(); 
msg.setDouble("Salary", salary); 
msg.setDouble("LoanAmount", loanAmt);
msg.setJMSReplyTo(responseQ);

Notice that the JMS message is created from the Session object via a factory method matching the message type. Instantiating a new JMS message object using the new keyword will not work; it must be created from the Session object. After creating and loading the message object, we are also setting the JMSReplyTo message header property to the response queue, which further decouples the producer from the consumer. The practice of setting the JMSReplyTo header property in the message producer as opposed to specifying the reply-to queue in the message consumer is a standard practice when using the request/reply model.

After the message is created, we then create the QueueSender object, specifying the queue we wish to send messages to, and then send the message using the send method:

QueueSender qSender = qSession.createSender(requestQ); 
qSender.send(msg);

There are several overridden send methods available in the QueueSender object. The one we are using here accepts only the JMS message object as the single argument. The other overridden methods allow you to specify the Queue, the delivery mode, the message priority, and finally the message expiry. Since we are not specifying any of the other values in the example just shown, the message priority is set to normal (4), the delivery mode is set to persistent messages (DeliveryMode.PERSISTENT), and the message expiry (time to live) is set to 0, indicating that the message will never expire. All of these parameters can be overridden by using one of the other send methods.

Once the message has been sent, the QBorrower class will block and wait for a response from the QLender on whether the loan was approved or denied. The first step in this process is to set up a message selector so that we can correlate the response message with the one we sent. This is necessary because there may be many other loan requests being sent to and from the loan request queues while we are making our loan request. To make sure we get the proper response back, we would use a technique called message correlation. Message correlation is required when using the request/reply model of point-to-point messaging where the queue is being shared by multiple producers and consumers (see Message Correlation for more details):

String filter = "JMSCorrelationID = '" + msg.getJMSMessageID() + "'"; 
QueueReceiver qReceiver = qSession.createReceiver(responseQ, filter); 

Notice we specify the filter when creating the QueueReceiver, indicating that we only want to receive messages when the JMSCorrelationID is equal to the original JMSMessageID. Now that we have a QueueReceiver, we can invoke the receive method to do a blocking wait until the response message is received. In this case, we are using the overridden receive method that accepts a timeout value in milliseconds:

TextMessage tmsg = (TextMessage)qReceiver.receive(30000); 
if (tmsg == null) { 
   System.out.println("QLender not responding"); 
} else { 
   System.out.println("Loan request was " + tmsg.getText()); 
}

It is a good idea to always specify a reasonable timeout value on the receive method; otherwise, it will sit there and wait forever (in effect, the application would “hang”). Specifying a reasonable timeout value allows the request/reply sender (in this case the QBorrower) to take action in the event the message has not been delivered in a timely fashion or there is a problem on the receiving side (in this case the QLender). If a timeout condition does occur, the message returned from the receive method will be null. Note that it is the entire message object that is null, not just the message payload. The receive method returns a Message object. If the message type is known, then you can cast the return message as we did in the preceding code example. However, a more failsafe technique would be to check the return Message type using the instanceof keyword as indicated here:

Message rmsg = qReceiver.receive(30000); 
if (rmsg == null) { 
   System.out.println("QLender not responding"); 
} else {
   if (rmsg instanceof TextMessage) { 
   TextMessage tmsg = (TextMessage)rmsg;
      System.out.println("Loan request was " + tmsg.getText());
   } else {
      throw new IllegalStateException("Invalid message type);
   } 
}

Notice that the message received does not need to be of the same message type as the one sent. In the example just shown, we sent the loan request using a MapMessage, yet we received the response from the receiver as a TextMessage. While you could potentially increase the level of decoupling between the sender and receiver by including the message type as part of the application properties of the message, you would still need to know how to interpret the payload in the message. For example, with a StreamMessage or BytesMessage you would still need to know the order of data being sent so that you could in turn read it in the proper order and data type. As you can guess, because of the “contract” of the data between the sender and receiver, there is still a fair amount of coupling in the point-to-point model, at least from the payload perspective.

The QLender Class

The role of the QLender class is to listen for loan requests on the loan request queue, determine if the salary meets the necessary business requirements, and finally send the results back to the borrower. Notice that the QLender class is structured a bit differently from the QBorrower class. In our example, the QLender class is referred to as a message listener and, as such, implements the javax.jms.MessageListener interface and overrides the onMessage() method. Here is the complete listing for the QLender class:

package ch04.p2p;

import java.io.*;
import javax.jms.*;
import javax.naming.*;

public class QLender implements MessageListener {

   private QueueConnection qConnect = null;    
   private QueueSession qSession = null;
   private Queue requestQ = null;

   public QLender(String queuecf, String requestQueue) {    
      try {
         // Connect to the provider and get the JMS connection
         Context ctx = new InitialContext();
         QueueConnectionFactory qFactory = (QueueConnectionFactory)
            ctx.lookup(queuecf);
         qConnect = qFactory.createQueueConnection();

         // Create the JMS Session
         qSession = qConnect.createQueueSession(
            false, Session.AUTO_ACKNOWLEDGE);

         // Lookup the request queue
         requestQ = (Queue)ctx.lookup(requestQueue);

         // Now that setup is complete, start the Connection
         qConnect.start();

         // Create the message listener
         QueueReceiver qReceiver = qSession.createReceiver(requestQ);
         qReceiver.setMessageListener(this);

         System.out.println("Waiting for loan requests...");

      } catch (JMSException jmse) {
         jmse.printStackTrace(); 
         System.exit(1);
      } catch (NamingException jne) {
         jne.printStackTrace(); 
         System.exit(1);
      }
   }

   public void onMessage(Message message) {
      try {
         boolean accepted = false;

         // Get the data from the message
         MapMessage msg = (MapMessage)message;
         double salary = msg.getDouble("Salary");
         double loanAmt = msg.getDouble("LoanAmount");
         
         // Determine whether to accept or decline the loan
         if (loanAmt < 200000) {
            accepted = (salary / loanAmt) > .25; 
         } else {
            accepted = (salary / loanAmt) > .33;
         }
         System.out.println("" +
            "Percent = " + (salary / loanAmt) + ", loan is " 
            + (accepted ? "Accepted!" : "Declined"));
         
         // Send the results back to the borrower
         TextMessage tmsg = qSession.createTextMessage();
         tmsg.setText(accepted ? "Accepted!" : "Declined");
         tmsg.setJMSCorrelationID(message.getJMSMessageID());
         
         // Create the sender and send the message
         QueueSender qSender = 
            qSession.createSender((Queue)message.getJMSReplyTo());
         qSender.send(tmsg);

         System.out.println("\nWaiting for loan requests...");

      } catch (JMSException jmse) {
         jmse.printStackTrace(); 
         System.exit(1);
      } catch (Exception jmse) {
         jmse.printStackTrace(); 
         System.exit(1);
      }
   }
    
   private void exit() {
      try {
         qConnect.close();
      } catch (JMSException jmse) {
         jmse.printStackTrace();
      }
      System.exit(0);
   }

   public static void main(String argv[]) {
      String queuecf = null;
      String requestq = null;
      if (argv.length == 2) {
         queuecf = argv[0];
         requestq = argv[1];
      } else {
         System.out.println("Invalid arguments. Should be: ");
         System.out.println
            ("java QLender factory request_queue");
         System.exit(0);
      }
      
      QLender lender = new QLender(queuecf, requestq);
      
      try {
         // Run until enter is pressed
         BufferedReader stdin = new BufferedReader
            (new InputStreamReader(System.in));
         System.out.println ("QLender application started");
         System.out.println ("Press enter to quit application");
         stdin.readLine();
         lender.exit();
      } catch (IOException ioe) {
         ioe.printStackTrace();
      }
   }
}

The QLender class is what is referred to as an asynchronous message listener, meaning that unlike the prior QBorrower class it will not block when waiting for messages. This is evident from the fact that the QLender class implements the MessageListener interface and overrides the onMessage method.

The main method of the QLender class validates the command-line arguments and invokes the constructor by instantiating a new QLender class. It then keeps the primary thread alive until the enter key is pressed on the command line.

The constructor in the QLender class works much in the same way as the QBorrower class. The first part of the constructor establishes a connection to the provider, does a JNDI lookup to get the queue, creates a QueueSession, and starts the connection:

...
// Connect to the provider and get the JMS connection
Context ctx = new InitialContext();
QueueConnectionFactory qFactory = (QueueConnectionFactory)
   ctx.lookup(queuecf);
qConnect = qFactory.createQueueConnection();

// Create the JMS Session
qSession = qConnect.createQueueSession(
   false, Session.AUTO_ACKNOWLEDGE);

// Lookup the request queue
requestQ = (Queue)ctx.lookup(requestQueue);

// Now that setup is complete, start the Connection
qConnect.start();
...

Once the connection is started, the QLender class can begin to receive messages. However, before it can receive messages, it must be registered by the QueueReceiver as a message listener:

QueueReceiver qReceiver = qSession.createReceiver(requestQ); 
qReceiver.setMessageListener(this);

At this point, a separate listener thread is started. That thread will wait until a message is received, and upon receipt of a message, will invoke the onMessage method of the listener class. In this case, we set the message listener to the QLender class using the this keyword in the setMessageListener method. We could have easily delegated the messaging work to another class that implemented the MessageListener interface:

qReceiver.setMessageListener(someOtherClass);

When a message is received on the queue specified in the createReceiver method, the listener thread will asynchronously invoke the onMessage method of the listener class (in our case, the QLender class is also the listener class). The onMessage method first casts the message to a MapMessage (the message type we are expecting to receive from the borrower). It then extracts the salary and loan amount requested from the message payload, checks the salary to loan amount ratio, then determines whether to accept or decline the loan request:

...
public void onMessage(Message message) {
   try {
      boolean accepted = false;

      // Get the data from the message
      MapMessage msg = (MapMessage)message;
      double salary = msg.getDouble("Salary");
      double loanAmt = msg.getDouble("LoanAmount");
         
      // Determine whether to accept or decline the loan
      if (loanAmt < 200000) {
         accepted = (salary / loanAmt) > .25; 
      } else {
         accepted = (salary / loanAmt) > .33;
      }
      System.out.println("" +
         "Percent = " + (salary / loanAmt) + ", loan is " 
         + (accepted ? "Accepted!" : "Declined"));
      ...         

Again, to make this more failsafe, it would be better to check the JMS message type using the instanceof keyword in the event another message type was being sent to that queue:

if (message instanceof MapMessage) {
   //process request
} else {
   throw new IllegalArgumentException("unsupported message type");
}

Once the loan request has been analyzed and the results determined, the QLender class needs to send the response back to the borrower. It does this by first creating a JMS message to send. The response message does not need to be the same JMS message type as the loan request message that was received by the QLender. To illustrate this point the QLender returns a TextMessage back to the QBorrower:

TextMessage tmsg = qSession.createTextMessage(); 
tmsg.setText(accepted ? "Accepted!" : "Declined"); 

The next statement sets the JMSCorrelationID, which is the JMS header property that is used by the QBorrower class to filter incoming response messages:

tmsg.setJMSCorrelationID(message.getJMSMessageID()); 

Message correlation is discussed in more detail in the next section of this chapter.

Once the message is created, the onMessage method then sends the message to the response queue specified by the JMSReplyTo message header property. As you may remember, in the QBorrower class we set the JMSReplyTo header property when sending the original loan request. The QLender class can now use that property as the destination to send the response message to:

QueueSender qSender = 
   qSession.createSender((Queue)message.getJMSReplyTo()); 
qSender.send(tmsg); 

Get Java Message Service, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.