View Javadoc

1   /*
2    * $Id: AbstractJmsFunctionalTestCase.java 20340 2010-11-24 19:06:46Z pablo.kraan $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.transport.jms.integration;
12  
13  import org.mule.api.MuleMessage;
14  import org.mule.api.config.ConfigurationBuilder;
15  import org.mule.api.transaction.Transaction;
16  import org.mule.config.spring.SpringXmlConfigurationBuilder;
17  import org.mule.module.client.MuleClient;
18  import org.mule.tck.FunctionalTestCase;
19  import org.mule.transaction.TransactionCoordination;
20  import org.mule.util.ClassUtils;
21  import org.mule.util.CollectionUtils;
22  import org.mule.util.IOUtils;
23  import org.mule.util.StringUtils;
24  
25  import java.net.URL;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Properties;
32  
33  import javax.jms.Connection;
34  import javax.jms.DeliveryMode;
35  import javax.jms.Destination;
36  import javax.jms.JMSException;
37  import javax.jms.Message;
38  import javax.jms.MessageConsumer;
39  import javax.jms.MessageProducer;
40  import javax.jms.Session;
41  import javax.jms.TextMessage;
42  import javax.jms.Topic;
43  import javax.jms.TopicConnection;
44  import javax.jms.TopicPublisher;
45  import javax.transaction.HeuristicMixedException;
46  import javax.transaction.HeuristicRollbackException;
47  import javax.transaction.RollbackException;
48  import javax.transaction.SystemException;
49  
50  import org.apache.commons.logging.Log;
51  import org.apache.commons.logging.LogFactory;
52  import org.junit.After;
53  import org.junit.Before;
54  
55  /**
56   * This is the base class for all integration tests that are part of the JMS integration test suite.  This is
57   * a suite that can be run on multiple JMS providers since all configuration for the provider is abstracted into
58   * a single class which implements {@link org.mule.transport.jms.integration.JmsVendorConfiguration}.  The implementation
59   * of this class is loaded by looking for the classname in a properties file called 'jms-vendor-configs.txt'in the root
60   * classpath.
61   * <p/>
62   * This test case provides a number of support methods for testing Jms providers with Mule.  This implementation is based
63   * around the concept of scenarios.  Scenarios define an action or set of actions and are represented as implementations
64   * of {@link org.mule.transport.jms.integration.AbstractJmsFunctionalTestCase.Scenario}.  Scenarios can be combined to create
65   * a test.  The default scenarios are usually sufficient to create a test.  These are:
66   * {@link org.mule.transport.jms.integration.AbstractJmsFunctionalTestCase.ScenarioReceive}
67   * {@link org.mule.transport.jms.integration.AbstractJmsFunctionalTestCase.ScenarioNotReceive}
68   * {@link org.mule.transport.jms.integration.AbstractJmsFunctionalTestCase.ScenarioCommit}
69   * {@link org.mule.transport.jms.integration.AbstractJmsFunctionalTestCase.ScenarioRollback}
70   * {@link org.mule.transport.jms.integration.AbstractJmsFunctionalTestCase.NonTransactedScenario}
71   * <p/>
72   * This object will also add properties to the registry that can be accessed within XML config files using placeholders.
73   * The following properties are made available -
74   * <ul>
75   * <li>${inbound.destination} - the URI of the inbound destination (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
76   * <li>${outbound.destination} - the URI of the outbound destination (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration implementation)</li>
77   * <li>${middle.destination} - the URI of the middle destination (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
78   * <li>${middle2.destination} - the URI of a second middle destination 'middle2'.</li>
79   * <li>${middle3.destination} - the URI of a third middle destination 'middle3'.</li>
80   * <li>${broadcast.destination} - the URI of the broadcast topic (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
81   * <li>${protocol} - the protocol of the current messaging connector (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
82   * </ul>
83   * <p/>
84   * For each integration test there are 2 configuration files. One is provided by the JMS integration suite and defines the
85   * event flow for the test. The other is a vendor-specific config file that defines the connectors and possibly endpoints and
86   * transformers for the Jms connector being tested. These configurations are known as 'connector' files, they share the same
87   * file name as the generic configuration file prepended with 'connector-'.  The location of these files must be
88   * <p/>
89   * <code>
90   * integration/&lt;provider_name>/connector-&lt;event_flow_config_name></code>
91   * <p/>
92   * The 'provider_name' is obtained from the {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation.
93   * <p/>
94   * In order to know what objects to define in the 'connector-' files you must copy the connector files from the ActiveMQ (default)
95   * test suite and configure the objects to match the configuration in the ActiveMQ tests.  Note that the object names must
96   * be consistently the same for things to work.
97   */
98  public abstract class AbstractJmsFunctionalTestCase extends FunctionalTestCase
99  {
100 
101     public static final String DEFAULT_INPUT_MESSAGE = "INPUT MESSAGE";
102     public static final String DEFAULT_OUTPUT_MESSAGE = "OUTPUT MESSAGE";
103 
104     public static final String INBOUND_ENDPOINT_KEY = "inbound.destination";
105     public static final String OUTBOUND_ENDPOINT_KEY = "outbound.destination";
106 
107     public static final String MIDDLE_ENDPOINT_KEY = "middle.destination";
108     public static final String MIDDLE2_ENDPOINT_KEY = "middle2.destination";
109     public static final String MIDDLE3_ENDPOINT_KEY = "middle3.destination";
110     public static final String BROADCAST_TOPIC_ENDPOINT_KEY = "broadcast.topic.destination";
111 
112     protected static final Log logger = LogFactory.getLog("MULE_TESTS");
113 
114     protected JmsVendorConfiguration jmsConfig = null;
115     protected Scenario scenarioNoTx;
116 
117     protected Scenario scenarioCommit;
118     protected Scenario scenarioRollback;
119     protected Scenario scenarioNotReceive;
120     protected Scenario scenarioReceive;
121     protected boolean purgeQueuesOnPreSetUp = true;
122     protected boolean purgeQueuesOnTearDown = true;
123 
124     private MuleClient client = null;
125 
126     /**
127      * This test case is refactored to support multiple JMS providers.
128      */
129     private boolean multipleProviders = true;
130 
131     /**
132      * Finds the {@link org.mule.transport.jms.integration.JmsVendorConfiguration} instances to test with by looking
133      * in a file called "jms-vendor-configs.txt" which contains one or more fuly qualified classnames of
134      * {@link org.mule.transport.jms.integration.JmsVendorConfiguration} instances to load.
135      *
136      * @return a collection of {@link org.mule.transport.jms.integration.JmsVendorConfiguration} instance to test
137      * against.
138      *
139      * @throws Exception if the 'jms-vendor-configs.txt' cannot be loaded or the classes defined within that file
140      * are not on the classpath
141      *
142      * TODO this method can return more than one provider, but our test class can only handle one at a time
143      * IMPORTANT: Only set one class in 'jms-vendor-configs.txt'
144      */
145     public static Collection jmsProviderConfigs()
146     {
147         JmsVendorConfiguration[][] configs;
148         URL url = ClassUtils.getResource("jms-vendor-configs.txt", AbstractJmsFunctionalTestCase.class);
149 
150         if (url == null)
151         {
152             fail("Please specify the org.mule.transport.jms.integration.JmsVendorConfiguration " +
153                  "implementation to use in jms-vendor-configs.txt on classpaath.");
154             return CollectionUtils.EMPTY_COLLECTION;
155         }
156 
157         if (logger.isInfoEnabled())
158         {
159             logger.info("Parameterized test using: " + url);
160         }
161 
162         try
163         {
164             List classes = IOUtils.readLines(url.openStream());
165             configs = new JmsVendorConfiguration[1][classes.size()];
166             int i = 0;
167             for (Iterator iterator = classes.iterator(); iterator.hasNext(); i++)
168             {
169                 String cls = (String) iterator.next();
170                 configs[0][i] = (JmsVendorConfiguration) ClassUtils.instanciateClass(cls, ClassUtils.NO_ARGS);
171             }
172             return Arrays.asList(configs);
173         }
174         catch (Exception e)
175         {
176             fail("Please specify the org.mule.transport.jms.integration.JmsVendorConfiguration " +
177                  "implementation to use in jms-vendor-configs.txt on classpath: " + e.getMessage());
178             return CollectionUtils.EMPTY_COLLECTION;
179         }
180     }
181 
182     /**
183      * Since we are using JUnit 4, but the Mule Test Framework assumes JUnit 3, we
184      * need to explicitly call the setUp and tearDown methods
185      *
186      * @throws Exception if, well, anything goes wrong
187      */
188     @Before
189     public void before() throws Exception
190     {
191         super.setUp();
192     }
193 
194     /**
195      * Since we are using JUnit 4, but the Mule Test Framework assumes JUnit 3, we
196      * need to explicitly call the setUp and tearDown methods
197      *
198      * @throws Exception if, well, anything goes wrong
199      */
200     @After
201     public void after() throws Exception
202     {
203         super.tearDown();
204     }
205 
206     public AbstractJmsFunctionalTestCase()
207     {
208         // TODO jmsProviderConfigs() can return more than one provider, but our test class can only handle one at a time
209         this(((JmsVendorConfiguration[]) jmsProviderConfigs().iterator().next())[0]);
210     }
211 
212     public AbstractJmsFunctionalTestCase(JmsVendorConfiguration config)
213     {
214         setJmsConfig(config);
215         scenarioNoTx = new NonTransactedScenario();
216         scenarioCommit = new ScenarioCommit();
217         scenarioRollback = new ScenarioRollback();
218         scenarioNotReceive = new ScenarioNotReceive();
219         scenarioReceive = new ScenarioReceive();
220     }
221 
222     @Override
223     protected void suitePreSetUp() throws Exception
224     {
225         super.suitePreSetUp();
226 
227         if (purgeQueuesOnPreSetUp)
228         {
229             purge(getInboundQueueName());
230             purge(getOutboundQueueName());
231             // TODO DZ: get all of the queue/topic names from the Mule config and just purge those
232         }
233     }
234 
235     /**
236      * Adds the following properties to the registry so that the Xml configuration files can reference them.
237      * <p/>
238      * <ul>
239      * <li>${inbound.destination} - the URI of the inbound destination (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
240      * <li>${outbound.destination} - the URI of the outbound destination (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration implementation)</li>
241      * <li>${middle.destination} - the URI of the middle destination (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
242      * <li>${middle2.destination} - the URI of a second middle destination 'middle2'.</li>
243      * <li>${middle3.destination} - the URI of a third middle destination 'middle3'.</li>
244      * <li>${broadcast.destination} - the URI of the broadcast topic (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
245      * <li>${protocol} - the protocol of the current messaging connector (retrieved from an {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implementation)</li>
246      * </ul>
247      *
248      * @return
249      */
250     @Override
251     protected Properties getStartUpProperties()
252     {
253         Properties props = new Properties();
254         // Inject endpoint names into the config
255         props.put(INBOUND_ENDPOINT_KEY, getJmsConfig().getInboundEndpoint());
256         props.put(OUTBOUND_ENDPOINT_KEY, getJmsConfig().getOutboundEndpoint());
257         props.put(MIDDLE_ENDPOINT_KEY, getJmsConfig().getMiddleEndpoint());
258         props.put(MIDDLE2_ENDPOINT_KEY, getJmsConfig().getMiddleEndpoint() + "2");
259         props.put(MIDDLE3_ENDPOINT_KEY, getJmsConfig().getMiddleEndpoint() + "3");
260 
261         props.put(BROADCAST_TOPIC_ENDPOINT_KEY, getJmsConfig().getTopicBroadcastEndpoint());
262         props.put("protocol", getJmsConfig().getProtocol());
263 
264         Map p = getJmsConfig().getProperties();
265         if (p != null)
266         {
267             props.putAll(p);
268         }
269         return props;
270     }
271 
272     /**
273      * This creates a {@link org.mule.config.spring.SpringXmlConfigurationBuilder} as expected but also figures out
274      * which 'connector' configuration file to load with the event flow configuration (obtained from the overriding \
275      * class which implements {@link #getConfigResources()}).
276      *
277      * @return The config builder used to create the Mule instance for this test
278      * @throws Exception
279      */
280     @Override
281     protected ConfigurationBuilder getBuilder() throws Exception
282     {
283         if (multipleProviders)
284         {
285             final String configResource = getConfigResources();
286             // multiple configs arent' supported by this mechanism, validate and fail if needed
287             if (StringUtils.splitAndTrim(configResource, ",; ").length > 1)
288             {
289                 throw new IllegalArgumentException("Parameterized tests don't support multiple " +
290                                                    "config files as input: " + configResource);
291             }
292             String resources = configResource.substring(configResource.lastIndexOf("/") + 1);
293             resources = String.format("integration/%s/connector-%s,%s", getJmsConfig().getName(),
294                                       resources, getConfigResources());
295             SpringXmlConfigurationBuilder builder = new SpringXmlConfigurationBuilder(resources);
296             return builder;
297         }
298         else
299         {
300             return super.getBuilder();
301         }
302     }
303 
304     /**
305      * Returns the {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implemetation to be used
306      * with this test
307      *
308      * @return settings for this test
309      */
310     public final JmsVendorConfiguration getJmsConfig()
311     {
312         if (jmsConfig == null)
313         {
314             jmsConfig = createJmsConfig();
315         }
316         return jmsConfig;
317     }
318 
319     /**
320      * Sets the {@link org.mule.transport.jms.integration.JmsVendorConfiguration} implemetation to be used
321      * with this test
322      *
323      * @param jmsConfig the settings for this test
324      */
325     public final void setJmsConfig(JmsVendorConfiguration jmsConfig)
326     {
327         this.jmsConfig = jmsConfig;
328     }
329 
330     /**
331      * Overriding classes must override this or inject this object. It is responsible for creating the
332      * {@link org.mule.transport.jms.integration.JmsVendorConfiguration} instance to be used by this test.
333      *
334      * @return the settings for this test
335      */
336     protected JmsVendorConfiguration createJmsConfig()
337     {
338         // Overriding classes must override this or inject this object
339         return null;
340     }
341 
342     /**
343      * Create a connection factory for the Jms profider being tested.  This calls
344      * through to {@link org.mule.transport.jms.integration.JmsVendorConfiguration#getConnection(boolean, boolean)}
345      *
346      * @param topic whether to use a topic or queue connection factory, for 1.1
347      *              implementations this proerty can be ignored
348      * @param xa    whether to create an XA connection factory
349      * @return a new JMS connection
350      */
351     protected final Connection getConnection(boolean topic, boolean xa) throws Exception
352     {
353         checkConfig();
354         return getJmsConfig().getConnection(topic, xa);
355     }
356 
357     /**
358      * Returns the {@link #getInboundQueueName()} in the form of an endpoint URI i.e.
359      * jms://in.
360      * <p/>
361      * This calls through to {@link JmsVendorConfiguration#getInboundEndpoint()}
362      *
363      * @return the Inbound JMS endpoint
364      */
365     protected final String getInboundEndpoint()
366     {
367         checkConfig();
368         return getJmsConfig().getInboundEndpoint();
369     }
370 
371     /**
372      * Returns the {@link #getOutboundQueueName()} in the form of an endpoint URI i.e.
373      * jms://out.
374      * <p/>
375      * This calls through to {@link org.mule.transport.jms.integration.JmsVendorConfiguration#getOutboundEndpoint()}
376      *
377      * @return the Outbound JMS endpoint
378      */
379     protected final String getOutboundEndpoint()
380     {
381         checkConfig();
382         return getJmsConfig().getOutboundEndpoint();
383     }
384 
385     /**
386      * The test inbound queue name.  For consistency this should always be 'in'. Note that you need to make
387      * sure that this queue is available in the the JMS provider being tested.
388      * <p/>
389      * This calls through to {@link org.mule.transport.jms.integration.JmsVendorConfiguration#getInboundDestinationName()}
390      *
391      * @return The test inbound destination name
392      */
393     protected final String getInboundQueueName()
394     {
395         checkConfig();
396         return getJmsConfig().getInboundDestinationName();
397     }
398 
399     /**
400      * The test dead letter queue name.  For consistency this should always be 'dlq'. Note that you need to make
401      * sure that this queue is available in the the JMS provider being tested.
402      * <p/>
403      * This calls through to {@link org.mule.transport.jms.integration.JmsVendorConfiguration#getDeadLetterDestinationName()}
404      *
405      * @return The test inbound destination name
406      */    
407     protected final String getDeadLetterQueueName()
408     {
409         checkConfig();
410         return getJmsConfig().getDeadLetterDestinationName();
411     }    
412     
413     /**
414      * The test outbound queue name.  For consistency this should always be 'out'. Note that you need to make
415      * sure that this queue is available in the the JMS provider being tested.
416      * <p/>
417      * This calls through to {@link org.mule.transport.jms.integration.JmsVendorConfiguration#getOutboundDestinationName()}
418      *
419      * @return The test outbound destination name
420      */
421     protected final String getOutboundQueueName()
422     {
423         checkConfig();
424         return getJmsConfig().getOutboundDestinationName();
425     }
426 
427     /**
428      * Timeout in milliseconds used when checking that a message is NOT present. This is usually 1000-2000ms.
429      * It is customizable so that slow connections i.e. over a wAN can be accounted for.
430      * <p/>
431      * This calls through to {@link JmsVendorConfiguration#getSmallTimeout()}
432      *
433      * @return timeout in milliseconds used when checking that a message is NOT present
434      */
435     protected final long getSmallTimeout()
436     {
437         checkConfig();
438         return getJmsConfig().getSmallTimeout();
439 
440     }
441 
442     /**
443      * The timeout in milliseconds used when waiting for a message to arrive. This is usually 3000-5000ms.
444      * However, it is customizable so that slow connections i.e. over a wAN can be accounted for.
445      * <p/>
446      * This calls through to {@link JmsVendorConfiguration#getTimeout()}
447      *
448      * @return The timeout used when waiting for a message to arrive
449      */
450     protected final long getTimeout()
451     {
452         checkConfig();
453         return getJmsConfig().getTimeout();
454     }
455 
456     /**
457      * Ensures that the {@link org.mule.transport.jms.integration.JmsVendorConfiguration} instance is not null
458      * if it is an {@link IllegalStateException} will be thrown
459      */
460     protected void checkConfig()
461     {
462         if (getJmsConfig() == null)
463         {
464             throw new IllegalStateException("There must be a Jms Vendor config set on this test");
465         }
466     }
467 
468     protected void dispatchMessage() throws Exception
469     {
470         dispatchMessage(DEFAULT_INPUT_MESSAGE);
471     }
472 
473     protected void dispatchMessage(Object payload) throws Exception
474     {
475         dispatchMessage(payload, null);
476     }
477 
478     protected void dispatchMessage(Object payload, Properties props) throws Exception
479     {
480         client.dispatch(getInboundEndpoint(), payload, props);
481     }
482 
483     protected MuleMessage receiveMessage() throws Exception
484     {
485         return receiveMessage(DEFAULT_OUTPUT_MESSAGE);
486     }
487 
488     protected MuleMessage receiveMessage(Object expected) throws Exception
489     {
490         MuleMessage result = client.request(getOutboundEndpoint(), getTimeout());
491         assertNotNull(result);
492         assertNotNull(result.getPayload());
493         assertNull(result.getExceptionPayload());
494         assertEquals(expected, result.getPayload());
495         return result;
496     }
497 
498     protected MuleMessage receiveMessage(byte[] expected) throws Exception
499     {
500         MuleMessage result = client.request(getOutboundEndpoint(), getTimeout());
501         assertNotNull(result);
502         assertNotNull(result.getPayload());
503         assertNull(result.getExceptionPayload());
504         byte[] bytes = result.getPayloadAsBytes();
505         assertEquals("Wrong number of bytes", expected.length, bytes.length);
506         for (int i=0; i < expected.length; ++i)
507         {
508             assertEquals("Byte #" + i + " does not match", expected[i], bytes[i]);
509         }
510         return result;
511     }
512 
513     public void runAsynchronousDispatching() throws Exception
514     {
515         dispatchMessage();
516         receiveMessage();
517         MuleMessage result = client.request(getOutboundEndpoint(), getSmallTimeout());
518         assertNull(result);
519     }
520 
521     protected void doSetUp() throws Exception
522     {
523         super.doSetUp();
524         client = new MuleClient(muleContext);
525         Transaction tx = TransactionCoordination.getInstance().getTransaction();
526         if (tx != null)
527         {
528             TransactionCoordination.getInstance().unbindTransaction(tx);
529             logger.warn("Transaction was active when this test began");
530         }
531     }
532 
533     protected void doTearDown() throws Exception
534     {
535         if (purgeQueuesOnTearDown)
536         {
537             purge(getInboundQueueName());
538             purge(getOutboundQueueName());
539             purgeTopics();
540         }
541 
542         super.doTearDown();
543         if (client != null)
544         {
545             client.dispose();
546         }
547         Transaction tx = TransactionCoordination.getInstance().getTransaction();
548         if (tx != null)
549         {
550             TransactionCoordination.getInstance().unbindTransaction(tx);
551             logger.warn("Transaction was active when this test ended");
552         }
553     }
554 
555     protected MuleClient getClient()
556     {
557         return client;
558     }
559 
560     public void send(Scenario scenario) throws Exception
561     {
562         Connection connection = null;
563         try
564         {
565             connection = getConnection(false, false);
566             connection.start();
567             Session session = null;
568             try
569             {
570                 session = connection.createSession(scenario.isTransacted(), scenario.getAcknowledge());
571                 Destination destination = createInputDestination(session, scenario);
572                 MessageProducer producer = null;
573                 try
574                 {
575                     producer = session.createProducer(destination);
576                     if (scenario.isPersistent())
577                     {
578                         producer.setDeliveryMode(DeliveryMode.PERSISTENT);
579                     }
580                     scenario.send(session, producer);
581                 }
582                 finally
583                 {
584                     if (producer != null)
585                     {
586                         producer.close();
587                     }
588                 }
589             }
590             finally
591             {
592                 if (session != null)
593                 {
594                     session.close();
595                 }
596             }
597         }
598         finally
599         {
600             if (connection != null)
601             {
602                 connection.close();
603             }
604         }
605     }
606 
607     /**
608      * By default this will create a Queue, override to create a topic
609      *
610      * @param session
611      * @param scenario
612      * @return
613      * @throws JMSException
614      */
615     protected Destination createInputDestination(Session session, Scenario scenario) throws JMSException
616     {
617         return session.createQueue(scenario.getInputDestinationName());
618     }
619 
620     /**
621      * By default this will create a Queue, override to create a topic
622      *
623      * @param session
624      * @param scenario
625      * @return
626      * @throws JMSException
627      */
628     protected Destination createOutputDestination(Session session, Scenario scenario) throws JMSException
629     {
630         return session.createQueue(scenario.getOutputDestinationName());
631     }
632 
633      /**/
634     public Message receive(Scenario scenario) throws Exception
635     {
636         assertNotNull("scenario is null!", scenario);
637         Connection connection = null;
638         try
639         {
640             connection = getConnection(false, false);
641             connection.start();
642             Session session = null;
643             try
644             {
645                 session = connection.createSession(scenario.isTransacted(), scenario.getAcknowledge());
646                 Destination destination = createOutputDestination(session, scenario);
647                 MessageConsumer consumer = null;
648                 try
649                 {
650                     consumer = session.createConsumer(destination);
651                     return scenario.receive(session, consumer);
652                 }
653                 finally
654                 {
655                     if (consumer != null)
656                     {
657                         consumer.close();
658                     }
659                 }
660             }
661             finally
662             {
663                 if (session != null)
664                 {
665                     session.close();
666                 }
667             }
668         }
669         finally
670         {
671             if (connection != null)
672             {
673                 connection.close();
674             }
675         }
676     }
677 
678     /**
679      * Purge destinations for clean test setup. Especially applicable to WMQ tests, as messages from
680      * other tests may still exist from other tests' runs.
681      * <p/>
682      * Well-behaving tests should drain both inbound and outbound destinations, as well as any intermediary ones.
683      * Typically this method is called from {@link #suitePreSetUp()} and {@link #suitePostTearDown()}, with proper super calls.
684      * @param destination destination name without any protocol specifics
685      * @see #suitePreSetUp()
686      * @see #suitePostTearDown()
687      */
688     protected void purge(final String destination) throws JMSException
689     {
690         Connection c = null;
691         Session s = null;
692         try
693         {
694             logger.debug("purging queue : " + destination);
695             c = getConnection(false, false);
696             assertNotNull(c);
697             c.start();
698 
699             s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
700             Destination d = s.createQueue(destination);
701             MessageConsumer consumer = s.createConsumer(d);
702 
703             while (consumer.receiveNoWait() != null)
704             {
705                 logger.debug("Destination " + destination + " isn't empty, draining it");
706             }
707         }
708         catch (Exception e)        
709         {
710             logger.error("unable to purge : " + destination);
711         }
712         finally
713         {
714             if (c != null)
715             {
716                 c.stop();
717                 if (s != null)
718                 {
719                     s.close();
720                 }
721                 c.close();
722             }
723         }
724     }
725 
726     /**
727      * Purge all of the topics which are created during testing
728      * TODO DZ: we should be getting this list dynamically, and only calling them for the topic tests
729      * @throws Exception
730      */
731     protected void purgeTopics() throws Exception
732     {
733         String destination = "broadcast";
734         purgeTopic(destination, "Client1");
735         purgeTopic(destination, "Client2");
736         purgeTopic(destination, "mule.JmsConnectorC1.broadcast");
737         purgeTopic(destination, "mule.JmsConnectorC2.broadcast");
738     }
739 
740     /**
741      * Clear the specified topic
742      *
743      * @param destination
744      * @param topic
745      * @throws Exception
746      */
747     protected void purgeTopic(String destination, String topic) throws Exception
748     {
749         Connection c = null;
750         Session s = null;
751 
752         try
753         {
754             logger.debug("purging topic : " + topic);
755             c = (TopicConnection) getConnection(true, false);
756             if (c == null)
757             {
758                 logger.debug("could not create a connection to topic : " + destination);
759             }
760 
761             c.start();
762             s = ((TopicConnection) c).createTopicSession(true, Session.SESSION_TRANSACTED);
763 
764             logger.debug("created topic session");
765             Topic dest = s.createTopic(destination);
766             logger.debug("created topic destination");
767 
768             TopicPublisher t;
769 
770             if (client != null)
771             {
772                 client.dispose();
773             }
774 
775             MessageConsumer consumer = null;
776 
777             try
778             {
779                 consumer = s.createDurableSubscriber((Topic) dest, topic);
780                 logger.debug("created consumer");
781                 while (consumer.receiveNoWait() != null)
782                 {
783                     logger.debug("Topic " + topic + " isn't empty, draining it");
784                 }
785                 logger.debug("topic should be empty");
786                 consumer.close();
787                 s.unsubscribe(topic);
788             }
789             catch (JMSException e)
790             {
791                 logger.debug("could not unsubscribe : " + topic);
792             }
793         }
794 
795         finally
796         {
797             if (c != null)
798             {
799                 c.stop();
800                 if (s != null)
801                 {
802                     s.close();
803                 }
804                 c.close();
805             }
806         }
807         logger.debug("completed draining topic :" + topic);
808     }
809 
810     public boolean isMultipleProviders()
811     {
812         return multipleProviders;
813     }
814 
815     public void setMultipleProviders(boolean multipleProviders)
816     {
817         this.multipleProviders = multipleProviders;
818     }
819 
820     // /////////////////////////////////////////////////////////////////////////////////////////////////
821     // Test Scenarios
822     // /////////////////////////////////////////////////////////////////////////////////////////////////
823 
824     protected interface Scenario
825     {
826 
827         boolean isPersistent();
828 
829         void setPersistent(boolean persistent);
830 
831         String getInputDestinationName();
832 
833         void setInputDestinationName(String inputQueue);
834 
835         String getOutputDestinationName();
836 
837         void setOutputDestinationName(String outputQueue);
838 
839         int getAcknowledge();
840 
841         void send(Session session, MessageProducer producer)
842                 throws JMSException, SystemException, HeuristicMixedException, HeuristicRollbackException,
843                        RollbackException;
844 
845         Message receive(Session session, MessageConsumer consumer)
846                 throws JMSException, SystemException, HeuristicMixedException, HeuristicRollbackException,
847                        RollbackException;
848 
849         boolean isTransacted();
850     }
851 
852     protected abstract class AbstractScenario implements Scenario
853     {
854 
855         private String inputQueue = getInboundQueueName();
856         private String outputQueue = getOutboundQueueName();
857         private boolean persistent = false;
858 
859         public boolean isPersistent()
860         {
861             return persistent;
862         }
863 
864         public void setPersistent(boolean persistent)
865         {
866             this.persistent = persistent;
867         }
868 
869         public String getInputDestinationName()
870         {
871             return inputQueue;
872         }
873 
874         public String getOutputDestinationName()
875         {
876             return outputQueue;
877         }
878 
879         public void setInputDestinationName(String inputQueue)
880         {
881             this.inputQueue = inputQueue;
882         }
883 
884         public void setOutputDestinationName(String outputQueue)
885         {
886             this.outputQueue = outputQueue;
887         }
888 
889         public int getAcknowledge()
890         {
891             return Session.AUTO_ACKNOWLEDGE;
892         }
893 
894         public void send(Session session, MessageProducer producer) throws JMSException
895         {
896             producer.send(session.createTextMessage(DEFAULT_INPUT_MESSAGE));
897             applyTransaction(session);
898         }
899 
900         public Message receive(Session session, MessageConsumer consumer) throws JMSException
901         {
902             Message message = consumer.receive(getTimeout());
903             assertNotNull(message);
904             assertTrue(TextMessage.class.isAssignableFrom(message.getClass()));
905             assertEquals(DEFAULT_OUTPUT_MESSAGE, ((TextMessage) message).getText());
906             applyTransaction(session);
907             return message;
908         }
909 
910         abstract protected void applyTransaction(Session session) throws JMSException;
911     }
912 
913     protected class NonTransactedScenario extends AbstractScenario
914     {
915 
916         public boolean isTransacted()
917         {
918             return false;
919         }
920 
921         protected void applyTransaction(Session session) throws JMSException
922         {
923             // do nothing
924         }
925     }
926 
927     protected class ScenarioCommit extends AbstractScenario
928     {
929 
930         public boolean isTransacted()
931         {
932             return true;
933         }
934 
935         protected void applyTransaction(Session session) throws JMSException
936         {
937             session.commit();
938         }
939     }
940 
941     protected class ScenarioRollback extends AbstractScenario
942     {
943 
944         public boolean isTransacted()
945         {
946             return true;
947         }
948 
949         protected void applyTransaction(Session session) throws JMSException
950         {
951             session.rollback();
952         }
953     }
954 
955     protected class ScenarioNotReceive extends NonTransactedScenario
956     {
957 
958         @Override
959         public Message receive(Session session, MessageConsumer consumer) throws JMSException
960         {
961             Message message = consumer.receive(getSmallTimeout());
962             assertNull(message);
963             return message;
964         }
965     }
966 
967     protected class ScenarioReceive extends NonTransactedScenario
968     {
969 
970         @Override
971         public Message receive(Session session, MessageConsumer consumer) throws JMSException
972         {
973             Message message = consumer.receive(getTimeout());
974             assertNotNull(message);
975             return message;
976         }
977     }
978 }