Thursday, April 24, 2014

JMS Connection, Session, Destination, Producer and Consumer

In JMS, basically it is that the sender(producer) sends the messages to the receiver(consumer). But the sender and the receiver are completely decoupled. They do not need to know each other. The only common thing between them is the destination.

When you create a sender, you use the following steps:

  1. Get the connectionFactory. You can use IntialContext to get one.
  2. Create connection: connection = connectionFactory.createConnection()
  3. Create session: session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
  4. Get/Create the destination: destination = context.lookup(a_destination_property); or destinaiton = session.createTopic(topicName);
    -------------------------------------------------------------------------
  5. Create producer: producer = session.createProducer(destination);
  6. Create and send message: message = session.createTextMessage("hello world"); producer.send(message);

When you create a consumer, you use the following steps:

  1. Get the connectionFactory. You can use IntialContext to get one.
  2. Create connection: connection = connectionFactory.createConnection()
  3. Create session: session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
  4. Get/Create the destination: destination = context.lookup(a_destination_property); or destinaiton = session.createTopic(topicName);
    --------------------------------------------------------------------------
  5. Create consumer: consumer = session.createConsumer(destination);
  6. Receive message: message = consumer.receive(1000) or use message listeners:
    consumer.setMessageListener(new javax.jms.MessageListener()
            {
              public void onMessage(Message m)
              {
                try{
                   m.acknowledge();
                }catch(JMSException e){}
              }
             }

The first 4 steps of the sender and the consumer are the same!

When you create a listener, you use the same 5 steps as in creating a consumer. But in the next step, you just do the following:

6. Set listener: consumer.setMessageListener(new javax.jms.MessageListener(){ public void on Message(Message m){...} });

So an important question is: what is a destination? The sender and the receiver may be two processes running on the local machine. But where is this destination. Is this a process phisically running on the remote JMS server? How to manage this destination? For example, is the message retention policy performed on this destination instance?

When a connection is created, there is communication with the remote JMS provider. This can be seen from the following stacktrace when the connection fails to be created:

Caused by: javax.jms.JMSException: Failed to connect to the server at tcp://xxxxxx.com:48200
 at com.tibco.tibjms.TibjmsxLinkTcp._createSocket(TibjmsxLinkTcp.java:831)
 at com.tibco.tibjms.TibjmsxLinkTcp.connect(TibjmsxLinkTcp.java:922)
 at com.tibco.tibjms.TibjmsConnection._create(TibjmsConnection.java:1302)
 at com.tibco.tibjms.TibjmsConnection.(TibjmsConnection.java:4182)
 at com.tibco.tibjms.TibjmsxCFImpl._createImpl(TibjmsxCFImpl.java:209)
 at com.tibco.tibjms.TibjmsxCFImpl._createConnection(TibjmsxCFImpl.java:253)
 at com.tibco.tibjms.TibjmsConnectionFactory.createConnection(TibjmsConnectionFactory.java:58)
From this code, it can be seen that it is trying to create a socket.

Note: Now I got more information on this. A destination ( topic or queue ) is usually a permanent existence on the JMS provider. It is independent from any JMS connection and session. You can use JNDI to lookup for a destination. And vice versa, a JMS connection or session is independent from destination. You can just use a connection factory to create a JMS connection and session without knowing any destination. So how is a destination related to a session? They are related to each other when you create the JMS producer and consumer. At that time, the session is bound to the destination. That said, there are two other scenarios where you can actually create a destination using a session. You can create a dynamic destination or a temporary destination. This kind of destination seems not to be permanent and will be gone when the session is gone.

When a destination is created, it does not actually has any communication to the remote JMS provider. It can just be an invalid destination. For example, in the method session.createTopic("myTopicName"), the value of "myTopicName" can be any string. It will not throw exception. The actual communication to the remote JMS provider happens when a producer is created using the session and the destination. In the case of Tibco JMS, there is the following code in creating a producer:

 TibjmsMessageProducer _createProducer(TibjmsDestination tibjmsdestination)
        throws JMSException
 {
        ...
       TibjmsMessage tibjmsmessage1 = _connection._link.sendRequestMsg(tibjmsmessage, _connection._requestTimeout);
        ...
 }
In the above code, the _link object is of the abstract class TibjmsxLink whose only subclass is TibjmsxLinkTcp. And internally this TibjmsxLinkTcp uses the class TibjmsxStream to communicate to the remote site. An exception will be thrown if the destination is not valid when a producer is being created.

Question 1: When a JMS session is created, we can pass the Acknowledge mode. It is easy to understand the mode for a consumer because in the consumer code we can add code to acknowledge the message. But what is the use of the mode for a producer? Does the JMS provider acknowledge the producer?

Question 2: What is exactly a session? Note that it is not even required to have a destination when you create a session. You only need a connection to create a session. If we create a session and then create both the producer and the consumer using this same session, anything interesting will happen?

One observation. The sessionTransacted and sessionAcknowledgeMode need to be specified when you create a session. The JMS API does not let you change these two properties after a session is created. There is no "set" method for these properties.

Note on message ID:

The JMS provider will set the unique ID for each message. In the following test, I just let the producer send the same message object twice. The printed message still shows that they have different ID values:

code:

producer.send(message);
System.out.println("JMSmessageID: " + message.getJMSMessageID());
producer.send(message);
System.out.println("JMSmessageID: " + message.getJMSMessageID());

output:    
JMSmessageID: ID:EMS01-PMITST02.7C0051A50F852287AD:2
JMSmessageID: ID:EMS01-PMITST02.7C0051A50F852287AD:3