6 Writing a JMS-enabled JTR-Runner
6 Writing a JMS-enabled JTR-Runner
With JTR you can also easily test Message Driven Beans (MDBs) or any other JMS-based Java component, writing a so called JMS-enabled JTR-Runner.
Let’s see an example taken from the jtrTestBenchClient application.
The MDBTest JMS Runner
package jtr.test.jee.client;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TemporaryQueue;
import javax.jms.TextMessage;
import jtr.runners.AbstractJMSRunner;
import jtr.test.IOutcome;
/**
*
* @author Francesco Russo (frusso@dev.java.net)
*/
public class MDBTest extends AbstractJMSRunner {
public void test() throws Throwable {
// let's prepare receiving the response message
TemporaryQueue rplQueue = queueSess.createTemporaryQueue();
qRcvr = queueSess.createReceiver(rplQueue);
queueConn.start();
// let's send the message
QueueSender sender = queueSess.createSender(queue);
logger.info("Created a sender");
TextMessage msg = queueSess.createTextMessage(msgPayload+this.getName());
msg.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
msg.setJMSExpiration(1200000);
msg.setJMSReplyTo(rplQueue);
logger.info("Created a txt msg");
sender.send(msg);
queueSess.commit();
logger.info("Message sent");
sender.close();
Message m = qRcvr.receive();
System.out.println("Received a response message: "+((TextMessage)m).getText());
queueSess.commit();
qRcvr.close();
rplQueue.delete();
}
public void enrichOutcome(IOutcome outcome) {
outcome.setUserObject("Using enterprise "+getEnterprise().getUniqueName()+
"\nUsing jms "+getJmsConfig().getUniqueName());
}
public void receiveFailureNotification(Throwable throwable, String string) {
}
public void cleanupResources() {
try {
if(queueSess!=null)
queueSess.close();
if(queueConn!=null)
queueConn.stop();
} catch(JMSException e) {
logger.error("Error cleaning-up resources held by runner "+getName(),e);
}
}
private QueueConnectionFactory queueConnFactory;
private QueueConnection queueConn;
private QueueSession queueSess;
private Queue queue;
private String msgPayload;
private QueueReceiver qRcvr;
}
Extending jtr.runners.AbstractJMSRunner
The first thing to note is that this time our new runner is inheriting from jtr.runners.AbstractJMSRunner instead of jtr.runners.AbstractRunner.
Basically there are no changes from the developer’s point of view. As you can see there are no other differences in terms of APIs and methods that must be implemented. All the interesting things happen only under the hood.
The test() method defined by the above runner makes use of a number of JMS resources that are not directly managed by any of its methods. This can happen thanks to the JTR-runtime we are going to instruct by means of the jtr.xml file.
Preparing the jtr.xml file
<?xml version = '1.0' encoding = 'UTF-8'?>
<!-- JTRunner is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
-->
<test xmlns="http://jtrunner.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jtrunner.sourceforge.net file:./config/jtr.xsd"
runs="10">
<!-- JTR components factories -->
<factories>
<factory key="IAssignmentPolicyFactory"
fqn="jtr.assigner.impl.DefaultAssignmentPolicyFactory" />
<factory key="IOutcomeFactory" fqn="jtr.test.impl.DefaultOutcomeFactory" />
<factory key="IWsHelperFactory" fqn="jtr.ws.jaxws.JaxWsHelperFactory" />
<factory key="ITestCompletionListener
fqn="jtr.test.impl.DefaultTestCompletionListener" />
<factory key="ITestResultDisplayer"
fqn="jtr.test.impl.DefaultTestResultDisplayer" />
<factory key="ITestResultsExporter"
fqn="jtr.test.results.exporters.impl.ExcelExporter" />
<factory key="ITestScriptingEngine" fqn="jtr.script.impl.BshScriptEngine" />
<factory key="ITestStatFunctions"
fqn="jtr.lang.functions.impl.DefaultStatFunctionsFactory" />
</factories>
<runner runs="10" enterprise="jboss" jms="jms">
<runner-fqn>jtr.test.jee.client.MDBTest</runner-fqn>
<instance-count>2</instance-count>
<parameters-assignment-policy>
indexed
</parameters-assignment-policy>
<parameters>
<param name="sleepTime" value="50" />
<param name="msgPayload" value="transactional msg "/>
</parameters>
</runner>
<!-- Enterprise Configuration -->
<enterprise uniqueName="jboss">
<property name="java.naming.provider.url" value="jnp://localhost:1099/"/>
<property name="java.naming.factory.initial"
value="org.jnp.interfaces.NamingContextFactory"/>
<property name="java.naming.factory.url.pkgs" value="org.jboss.naming.client"/>
</enterprise>
<!-- JMS Configuration -->
<jms uniqueName="jms">
<destinations>
<queues>
<queue property="queue" jndi="queue/fruxoTestQueue" />
</queues>
</destinations>
<conn-factories>
<conn-factory property="queueConnFactory" jndi="QueueConnectionFactory">
<connections>
<connection shared="false" clientId="${runner.id}" property="queueConn"
type="queue">
<sessions>
<!-- session attributes:
1. type: (generic|queue|topic|xa|xaQueue|xaTopic)
2. property: as above
3. transacted: (true|false)
4. ackMode: (1,2,3 respectively AUTO_ACKNOWLEDGE,
CLIENT_ACKNOWLEDGE,
DUPS_OK_ACKNOWLEDGE) -->
<session property="queueSess"
ackMode="1"
type="queue"
transacted="true"/>
</sessions>
</connection>
</connections>
</conn-factory>
</conn-factories>
</jms>
</test>
In the above jtr.xml file we introduce the new runner category jtr.test.jee.client.MDBTest. We declare we want 2 instances of this category active at runtime. Furthermore this category declares the use of the jboss enterprise-configuration and of the jms jms-configuration.
Binding a JMS-enabled runner with bothe an enterprise and a jms configuration
In order to enable the JTR-runtime to provide your JMS-runners with those JMS-resources they require, you must declare two bindings in the jtr.xml configuration file:
•enterprise: this binding defines the set of information required to correctly perform the lookup of all the resources declared in the jms binding
•jms: this binding defines all the JMS resources your runner(s) actually requires
The enterprise binding found in the example above is the same binding used in the former section to enable the JTR-runtime to look-up the target EJBs.
The jms binding called “jms” instead defines the set of JMS-resources needed and used by the MDBTest runner showed before.
Note: one of the first things we can point out before delving into the details of the jms element and its sub-elements is that every JMS-resource declared in the provided example has a property attribute that meets a Java property located in the MDBTest class. This means that the JMS-resource associated with that property attribute will be obtained by the JTR-runtime and injected into your runners instances on your behalf, bereaving you from the burden of directly managing all these repetitive tasks.
About the jms jtr.xml section
The figure above depicts the grammar defined by the jtr.xsd file for a JTR jms-configuration section.
The jms element only requires a unique the name, the name that should be used in the jtr.xml file to reference that JMS-configuration.
The jms element basically accepts two sub-elements: destinations and conn-factories.
The destinations element acts as a container of various JMS-destinations, organized in queues and topics.
The queues element accepts one or more queue sub-elements, each declaring a JMS queue that must be obtained and injected into the runner instances using this configuration. For this purpose the queue element has a property attribute that identifies the property the runner class using this JMS configuration must either define or inherit, along with the jndi name required for looking-up the JMS queue.
The topics element is equivalent to the queues element with the exception that defines topics instead of queues.
The conn-factories sub-element defines JMS connection factories that must be retrieved and used to create both connections and sessions on behalf of the requiring runner instances. This element requires two attributes, property and jndi, with the same meaning discussed for the queue element.
The connections sub-element acts as a container for JMS-connections that should be made available to the requiring runners.
Each connection requires the following attributes:
•type: legal values are queue | topic | xaQueue | xaTopic
•property: the name of the Java property the runner must either define or inherit that will hold this JMS connection
•userName: the user name (if required) to use to establish the connection or an empty string
•password: the password (if required) to use to establish the connection or an empty string
•clientId: the JMS client-id that should be associated with the connection or the mnemonic {$runner.id}
•shared: whether this connection should be shared or not amongst different runners. Legal values are true | false
Given a connection it is possible to open as many sessions as you need. You achieve this by means of the sessions element that acts as a container of one or many session elements.
Each session element requires the following attributes:
•type: legal values are generic | queue | topic | xa | xaQueue | xaTopic
•property: the same as above
•transacted: whether this session is transacted or not. Legal values are true | false
•ackMode: indicates whether the consumer or the client will acknowledge any messages it receives; ignored if the session is transacted. Legal values are 1 | 2 | 3 respectively for Session.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE, and Session.DUPS_OK_ACKNOWLEDGE.
{$runner.id}: about sharing jtr.xml jms-configurations
It is possible to define a single jms-configuration and bind different runner instances or different runner categories to that jms-configuration.
In this case, should the jms-configuration declare an unshared connection, remember to use as clientId the mnemonic {$runner.id} rather than hardcoding a specific value in the jtr.xml file. In this way, the {$runner.id} mnemonic is replaced by the JTR-runtime with the unique name associated with the served runner instance. This unique name can be obtained via the jtr.runners.IRunner.getName() method.