View Javadoc

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