Note that this script supports the queuing/point-to-point model of JMS (rather than the publish and subscribe model), works with JMS version 1.1 rather than 1.02b (as it uses the BytesMessage.getBodyLength() method among others), and has been tested with Java 1.5 using the Tibco implementation of JMS.
The LoadRunner script can be downloaded here, the source code is available here, and a mirror of the JMS 1.1 specification can be downloaded here. People using Tibco’s JMS implementation can download their Javadocs here (10MB). Feedback is appreciated.
From looking at the script, you may wonder why I chose to write my own script using the Java vuser type instead of using the Web Services vuser (version 8.1, Feature Pack 4 supports web services using JMS as well as HTTP), but that is a story for some other time.
Anyway…for the tiny number of people that I haven’t lost already, here’s how it all works…
At a high level, we are trying to put a SOAP message on a queue (MyLoadTest.Warehouse.Request), receive the corresponding response from a different queue (MyLoadTest.Warehouse.Response), and check that the response message matches our expected message. Download my source code and follow along.
On with the detail…
The javax.jms.* classes provide interfaces only, other vendors have provided implementations of these interfaces (I am using Tibco’s implementation – found in com.tibco.tibjms.*). This should mean that any JMS code you write will be portable between implementations; whether this is true or not is something I haven’t tested.
Initially we perform a JNDI lookup to find a ConnectionFactory and get the details for the queues we will be using.
We then create a Connection and a Session, and then create the MessageProducer (using the request queue details) that we will be using to send our message to the request queue.
We create either BytesMessage or a TextMessage (MapMessage, ObjectMessage and StreamMessage have not been implemented in the script), and set the JMS header properties. The important JMS headers are the JMSDestination (the request queue), and the JMSReplyTo (the response queue). We can also set Message properties, which are simple name/value pairs. We then set the Message body, using a string constant containing the SOAP XML message.
We only want to measure system response times, so we only put our LoadRunner transaction timing points around the code that sends and receives the message.
Sending the message is as simple as calling myMessageProducer.send(myMessage).
In the following extract from the Tibco EMS logs, you can see the JMS headers, the Message properties and the Message body (shown here with a size only). The JMS headers show the request and response queues along with a unique msgID.
2007-02-26 13:20:07 [MSG:73422]
received from user='anonymous':
To receive a Message, we must create a MessageConsumer (using the response queue). Rather than simply receiving the messages from the queue in the order they arrived (FIFO – which would mean that we could be receiving the wrong response message when we are running load against the system), we can use a message selector to make sure that we receive the message that we want from the queue.
In the log above, we saw that each JMS message has a unique JMSMessageID. When a response is sent by the server, the response message has a JMSCorrelationID that matches the JMSMessageID of the message it is replying to. A message selector is defined in the same way as the WHERE clause of an SQL statement, so our selector would be
"JMSCorrelationID = '" + myBytesMessage.getMessageID() + "'".
Note that the unique message ID is only set when the message is sent so, if you are using a selector, you must create your MessageConsumer *after* you have called myMessageProducer.send(myMessage). A selector can use any of the JMS headers and Message properties, but cannot use the Message body.
Using the MessageConsumer, receiving the correct message from the queue is done by simply calling myMessageConsumer.receive(timeout). The script will wait to receive the message until it reaches the timeout value. If no message is received before the timeout, the response message will be null. Remember to close the MessageConsumer, or you will leak and end your test with thousands of open receivers.
Here is the Tibco EMS log entry for the response message. You can see that the message has a different JMSMessageID, and has a JMSCorrelationID with the message ID of the request it is replying to.
2007-02-26 13:20:09 [MSG:73425]
received from user='anonymous':
Extracting contents of the Message body from the JMS Message is done by calling myMessage.getText() if it is a TextMessage (if it is a BytesMessage, it is a bit trickier – see the attached script).
And finally comparing the received SOAP message to the expected message is just done using String.equals().
Remember to close the Session and Connection at the end of the script.
The exercise of modifying the script to send many different messages to different queues (which would mean you only have to maintain one script instead of one script per SOAP operation) is left as an exercise for the reader…