1   /*
2    * $Id: AbstractJmsTransactionFunctionalTest.java 7963 2007-08-21 08:53:15Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.test.integration.providers.jms;
12  
13  import org.mule.MuleManager;
14  import org.mule.config.MuleProperties;
15  import org.mule.impl.DefaultExceptionStrategy;
16  import org.mule.impl.MuleDescriptor;
17  import org.mule.impl.MuleTransactionConfig;
18  import org.mule.impl.endpoint.MuleEndpoint;
19  import org.mule.impl.endpoint.MuleEndpointURI;
20  import org.mule.providers.jms.JmsConnector;
21  import org.mule.providers.jms.MessageRedeliveredException;
22  import org.mule.providers.jms.transformers.JMSMessageToObject;
23  import org.mule.providers.jms.transformers.ObjectToJMSMessage;
24  import org.mule.tck.functional.EventCallback;
25  import org.mule.tck.functional.FunctionalTestComponent;
26  import org.mule.test.integration.providers.jms.tools.JmsTestUtils;
27  import org.mule.transaction.TransactionCoordination;
28  import org.mule.umo.UMOComponent;
29  import org.mule.umo.UMODescriptor;
30  import org.mule.umo.UMOEventContext;
31  import org.mule.umo.UMOException;
32  import org.mule.umo.UMOMessage;
33  import org.mule.umo.UMOTransaction;
34  import org.mule.umo.UMOTransactionConfig;
35  import org.mule.umo.UMOTransactionFactory;
36  import org.mule.umo.endpoint.MalformedEndpointException;
37  import org.mule.umo.endpoint.UMOEndpoint;
38  import org.mule.umo.endpoint.UMOEndpointURI;
39  import org.mule.umo.manager.UMOManager;
40  import org.mule.umo.provider.UMOConnector;
41  import org.mule.util.ExceptionUtils;
42  
43  import java.util.HashMap;
44  
45  import javax.jms.JMSException;
46  import javax.jms.Message;
47  import javax.jms.MessageConsumer;
48  import javax.jms.MessageListener;
49  import javax.jms.QueueConnection;
50  import javax.jms.Session;
51  import javax.jms.TextMessage;
52  import javax.jms.TopicConnection;
53  
54  import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
55  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
56  
57  /**
58   * <code>AbstractJmsTransactionFunctionalTest</code> is a base class for all JMS
59   * based functional tests with or without transactions.
60   */
61  
62  public abstract class AbstractJmsTransactionFunctionalTest extends AbstractJmsFunctionalTestCase
63  {
64  
65      public static final int SEND_NOT_TRANSACTED = 0x01;
66      public static final int SEND_TRANSACTED_ALWAYS = 0x02;
67      public static final int SEND_TRANSACTED_IF_POSSIBLE_WITH_TRANSACTION = 0x04;
68      public static final int SEND_TRANSACTED_IF_POSSIBLE_WITHOUT_TRANSACTION = 0x08;
69      public static final int SEND_TRANSACTED_ROLLBACK = 0x10;
70      public static final int CLEANUP = 0x20;
71      public static final int TRANSACTED_REDELIVERY_TO_DL_DESTINATION = 0x40;
72      public static final int ALL = 0xffff;
73  
74      protected volatile UMOTransaction currentTx;
75      protected int exclusionFlag = 0;
76  
77      protected void exclude(int flag)
78      {
79          exclusionFlag = flag;
80      }
81  
82      protected boolean notExcluded(int flag)
83      {
84          boolean excluded = (flag & exclusionFlag) != 0;
85          if (excluded)
86          {
87              logger.warn("Excluding this test");
88          }
89          return ! excluded;
90      }
91  
92      protected void doSetUp() throws Exception
93      {
94          super.doSetUp();
95          currentTx = null;
96      }
97  
98      protected void doTearDown() throws Exception
99      {
100         TransactionCoordination.getInstance().unbindTransaction(
101             TransactionCoordination.getInstance().getTransaction());
102         super.doTearDown();
103     }
104 
105     public void testSendNotTransacted() throws Exception
106     {
107         if (notExcluded(SEND_NOT_TRANSACTED))
108         {
109             UMODescriptor descriptor = getDescriptor("testComponent", FunctionalTestComponent.class.getName());
110 
111             final int countDownInitialCount = 2;
112             final CountDownLatch countDown = new CountDownLatch(countDownInitialCount);
113 
114             EventCallback callback = new EventCallback()
115             {
116                 public synchronized void eventReceived(UMOEventContext context, Object component)
117                 {
118                     callbackCalled = true;
119                     assertNull(context.getCurrentTransaction());
120                     countDown.countDown();
121                 }
122             };
123 
124             initialiseComponent(descriptor, UMOTransactionConfig.ACTION_NONE, callback);
125             addResultListener(getOutDest().getAddress(), countDown);
126             MuleManager.getInstance().start();
127             afterInitialise();
128             send(DEFAULT_MESSAGE, false, Session.AUTO_ACKNOWLEDGE);
129 
130             countDown.await(LOCK_WAIT, TimeUnit.MILLISECONDS);
131             assertTrue("Only " + (countDownInitialCount - countDown.getCount()) + " of " + countDownInitialCount
132                     + " checkpoints hit", countDown.getCount() == 0);
133 
134             assertNotNull(currentMsg);
135             assertTrue(currentMsg instanceof TextMessage);
136             assertEquals(DEFAULT_MESSAGE + " Received", ((TextMessage)currentMsg).getText());
137             assertTrue(callbackCalled);
138             assertNull(currentTx);
139         }
140     }
141 
142     public void testSendTransactedAlways() throws Exception
143     {
144         if (notExcluded(SEND_TRANSACTED_ALWAYS))
145         {
146             final int countDownInitialCount = 2;
147             final CountDownLatch countDown = new CountDownLatch(countDownInitialCount);
148 
149             // setup the component and start Mule
150             UMODescriptor descriptor = getDescriptor("testComponent", FunctionalTestComponent.class.getName());
151 
152             EventCallback callback = new EventCallback()
153             {
154                 public synchronized void eventReceived(UMOEventContext context, Object component) throws Exception
155                 {
156                     callbackCalled = true;
157                     currentTx = context.getCurrentTransaction();
158                     assertNotNull(currentTx);
159                     assertTrue(currentTx.isBegun());
160                     countDown.countDown();
161                 }
162             };
163 
164             initialiseComponent(descriptor, UMOTransactionConfig.ACTION_ALWAYS_BEGIN, callback);
165 
166             // Start the server
167             MuleManager.getInstance().start();
168             addResultListener(getOutDest().getAddress(), countDown);
169 
170             // Send a test message first so that it is there when the component is
171             // started
172             send(DEFAULT_MESSAGE, false, getAcknowledgementMode());
173 
174             countDown.await(LOCK_WAIT, TimeUnit.MILLISECONDS);
175             assertTrue("Only " + (countDownInitialCount - countDown.getCount()) + " of " + countDownInitialCount
176                     + " checkpoints hit", countDown.getCount() == 0);
177 
178             assertNotNull(currentMsg);
179             assertTrue(currentMsg instanceof TextMessage);
180             assertEquals(DEFAULT_MESSAGE + " Received", ((TextMessage)currentMsg).getText());
181             assertTrue(callbackCalled);
182             assertTrue(currentTx.isBegun());
183             // TODO for some reason, it takes a while for committed flag on the tx
184             // to update
185             Thread.sleep(1000);
186             assertTrue(currentTx.isCommitted());
187         }
188     }
189 
190     public void testSendTransactedIfPossibleWithTransaction() throws Exception
191     {
192         if (notExcluded(SEND_TRANSACTED_IF_POSSIBLE_WITH_TRANSACTION))
193         {
194             doSendTransactedIfPossible(true);
195         }
196     }
197 
198     public void testSendTransactedIfPossibleWithoutTransaction() throws Exception
199     {
200         if (notExcluded(SEND_TRANSACTED_IF_POSSIBLE_WITHOUT_TRANSACTION))
201         {
202             doSendTransactedIfPossible(false);
203         }
204     }
205 
206     private void doSendTransactedIfPossible(final boolean transactionAvailable) throws Exception
207     {
208         final int countDownInitialCount = 2;
209         final CountDownLatch countDown = new CountDownLatch(countDownInitialCount);
210 
211         // setup the component and start Mule
212         UMODescriptor descriptor = getDescriptor("testComponent", FunctionalTestComponent.class.getName());
213 
214         EventCallback callback = new EventCallback()
215         {
216             public synchronized void eventReceived(UMOEventContext context, Object component) throws Exception
217             {
218                 callbackCalled = true;
219                 currentTx = context.getCurrentTransaction();
220                 if (transactionAvailable)
221                 {
222                     assertNotNull(currentTx);
223                     assertTrue(currentTx.isBegun());
224                 }
225                 else
226                 {
227                     assertNull(currentTx);
228                 }
229                 countDown.countDown();
230             }
231         };
232 
233         initialiseComponent(descriptor, (transactionAvailable
234                         ? UMOTransactionConfig.ACTION_ALWAYS_BEGIN : UMOTransactionConfig.ACTION_NONE),
235             callback);
236 
237         // Start the server
238         MuleManager.getInstance().start();
239         addResultListener(getOutDest().getAddress(), countDown);
240 
241         // Send a test message firstso that it is there when the component is
242         // started
243         send(DEFAULT_MESSAGE, false, Session.AUTO_ACKNOWLEDGE);
244 
245         countDown.await(LOCK_WAIT, TimeUnit.MILLISECONDS);
246         assertTrue("Only " + (countDownInitialCount - countDown.getCount()) + " of " + countDownInitialCount
247                    + " checkpoints hit", countDown.await(LOCK_WAIT, TimeUnit.MILLISECONDS));
248 
249         assertNotNull(currentMsg);
250         assertTrue(currentMsg instanceof TextMessage);
251         assertEquals(DEFAULT_MESSAGE + " Received", ((TextMessage)currentMsg).getText());
252         assertTrue(callbackCalled);
253 
254         if (transactionAvailable)
255         {
256             assertNotNull(currentTx);
257             assertTrue(currentTx.isBegun());
258             // TODO for some reason, it takes a while for committed flag on the
259             // tx to update
260             Thread.sleep(300);
261             assertTrue(currentTx.isCommitted());
262         }
263         else
264         {
265             assertNull(currentTx);
266         }
267     }
268 
269     public void testSendTransactedRollback() throws Exception
270     {
271         if (notExcluded(SEND_TRANSACTED_ROLLBACK))
272         {
273             final int countDownInitialCount = 2;
274             final CountDownLatch countDown = new CountDownLatch(countDownInitialCount);
275 
276             // This exception strategy will be invoked when a message is redelivered
277             // after a rollback
278 
279             // setup the component and start Mule
280             UMODescriptor descriptor = getDescriptor("testComponent", FunctionalTestComponent.class.getName());
281 
282             EventCallback callback = new EventCallback()
283             {
284                 public synchronized void eventReceived(UMOEventContext context, Object component) throws Exception
285                 {
286                     callbackCalled = true;
287                     currentTx = context.getCurrentTransaction();
288                     assertNotNull(currentTx);
289                     assertTrue(currentTx.isBegun());
290                     logger.debug("@@@@ Rolling back transaction @@@@");
291                     currentTx.setRollbackOnly();
292                     countDown.countDown();
293                 }
294             };
295 
296             initialiseComponent(descriptor, UMOTransactionConfig.ACTION_ALWAYS_BEGIN, callback);
297             UMOManager manager = MuleManager.getInstance();
298             addResultListener(getOutDest().getAddress(), countDown);
299 
300             UMOConnector umoCnn = manager.lookupConnector(CONNECTOR_NAME);
301             // Set the test Exception strategy
302             umoCnn.setExceptionListener(new RollbackExceptionListener(countDown));
303 
304             // Start the server
305             manager.start();
306 
307             // Send a test message firstso that it is there when the component is
308             // started
309             send(DEFAULT_MESSAGE, false, Session.AUTO_ACKNOWLEDGE);
310 
311             afterInitialise();
312             countDown.await(LOCK_WAIT, TimeUnit.MILLISECONDS);
313             assertTrue("Only " + (countDownInitialCount - countDown.getCount()) + " of " + countDownInitialCount
314                     + " checkpoints hit", countDown.getCount() == 0);
315 
316             // Sleep a while to allow transaction to be rolled back
317             afterInitialise();
318 
319             assertNull(currentMsg);
320             assertTrue(callbackCalled);
321             assertTrue(currentTx.isRolledBack());
322 
323             // Make sure the message isn't on the queue
324             assertNull(receive(getInDest().getAddress(), 2000));
325         }
326     }
327 
328     public void testCleanup() throws Exception
329     {
330         if (notExcluded(CLEANUP))
331         {
332             assertNull("There should be no transaction associated with this thread",
333                     TransactionCoordination.getInstance().getTransaction());
334         }
335     }
336 
337     public UMOComponent initialiseComponent(UMODescriptor descriptor,
338                                             byte txBeginAction,
339                                             EventCallback callback) throws Exception
340     {
341         JMSMessageToObject inTrans = new JMSMessageToObject();
342         ObjectToJMSMessage outTrans = new ObjectToJMSMessage();
343 
344         UMOEndpoint endpoint = new MuleEndpoint("testIn", getInDest(), connector, inTrans,
345             UMOEndpoint.ENDPOINT_TYPE_RECEIVER, 0, null, null);
346 
347         UMOTransactionConfig txConfig = new MuleTransactionConfig();
348         txConfig.setFactory(getTransactionFactory());
349         txConfig.setAction(txBeginAction);
350 
351         UMOEndpoint outProvider = new MuleEndpoint("testOut", getOutDest(), connector, outTrans,
352             UMOEndpoint.ENDPOINT_TYPE_SENDER, 0, null, null);
353 
354         endpoint.setTransactionConfig(txConfig);
355 
356         descriptor.setOutboundEndpoint(outProvider);
357         descriptor.setInboundEndpoint(endpoint);
358         HashMap props = new HashMap();
359         props.put("eventCallback", callback);
360         descriptor.setProperties(props);
361         UMOComponent component = model.registerComponent(descriptor);
362         // MuleManager.getInstance().registerConnector(connector);
363         return component;
364     }
365 
366     public static UMODescriptor getDescriptor(String name, String implementation)
367     {
368         UMODescriptor descriptor = new MuleDescriptor();
369         descriptor.setExceptionListener(new DefaultExceptionStrategy());
370         descriptor.setName(name);
371         descriptor.setImplementation(implementation);
372         return descriptor;
373     }
374 
375     public void afterInitialise() throws Exception
376     {
377         Thread.sleep(1000);
378     }
379 
380     protected UMOEndpointURI getInDest()
381     {
382         try
383         {
384             if (cnn instanceof QueueConnection)
385             {
386                 return new MuleEndpointURI(DEFAULT_IN_QUEUE);
387             }
388             else
389             {
390                 return new MuleEndpointURI(DEFAULT_IN_TOPIC);
391             }
392         }
393         catch (MalformedEndpointException e)
394         {
395             fail(e.getMessage());
396             return null;
397         }
398     }
399 
400     protected UMOEndpointURI getOutDest()
401     {
402         try
403         {
404             if (cnn instanceof QueueConnection)
405             {
406                 return new MuleEndpointURI(DEFAULT_OUT_QUEUE);
407             }
408             else
409             {
410                 return new MuleEndpointURI(DEFAULT_OUT_TOPIC);
411             }
412         }
413         catch (MalformedEndpointException e)
414         {
415             fail(e.getMessage());
416             return null;
417         }
418     }
419 
420     protected UMOEndpointURI getDLDest()
421     {
422         try
423         {
424             if (cnn instanceof QueueConnection)
425             {
426                 return new MuleEndpointURI(DEFAULT_DL_QUEUE);
427             }
428             else
429             {
430                 return new MuleEndpointURI(DEFAULT_DL_TOPIC);
431             }
432         }
433         catch (MalformedEndpointException e)
434         {
435             fail(e.getMessage());
436             return null;
437         }
438     }
439 
440     protected void send(String payload, boolean transacted, int ack) throws JMSException
441     {
442         if (cnn instanceof QueueConnection)
443         {
444             JmsTestUtils.queueSend((QueueConnection)cnn, getInDest().getAddress(), payload, transacted, ack,
445                 null);
446         }
447         else
448         {
449             JmsTestUtils.topicPublish((TopicConnection)cnn, getInDest().getAddress(), payload, transacted,
450                 ack);
451         }
452     }
453 
454     protected int getAcknowledgementMode()
455     {
456         return Session.AUTO_ACKNOWLEDGE;
457     }
458 
459     protected void addResultListener(String dest, final CountDownLatch countDown) throws JMSException
460     {
461         MessageConsumer mc;
462         // check replyTo
463         if (useTopics())
464         {
465             mc = JmsTestUtils.getTopicSubscriber((TopicConnection)cnn, dest);
466         }
467         else
468         {
469             mc = JmsTestUtils.getQueueReceiver((QueueConnection)cnn, dest);
470         }
471         mc.setMessageListener(new MessageListener()
472         {
473             public void onMessage(Message message)
474             {
475                 currentMsg = message;
476                 if (countDown != null)
477                 {
478                     countDown.countDown();
479                 }
480             }
481         });
482     }
483 
484     public abstract UMOTransactionFactory getTransactionFactory();
485 
486     public class RollbackExceptionListener extends DefaultExceptionStrategy
487     {
488         private CountDownLatch countDown;
489 
490         public RollbackExceptionListener(CountDownLatch countDown)
491         {
492             this.countDown = countDown;
493         }
494 
495         public RollbackExceptionListener(CountDownLatch countDown, UMOEndpointURI deadLetter)
496             throws UMOException
497         {
498             this.countDown = countDown;
499             UMOEndpoint ep = MuleEndpoint.createEndpointFromUri(deadLetter, UMOEndpoint.ENDPOINT_TYPE_SENDER);
500             // lets include dispatch to the deadLetter queue in the sme tx.
501             ep.setTransactionConfig(new MuleTransactionConfig());
502             ep.getTransactionConfig().setAction(UMOTransactionConfig.ACTION_JOIN_IF_POSSIBLE);
503             super.addEndpoint(ep);
504         }
505 
506         public void handleMessagingException(UMOMessage message, Throwable t)
507         {
508             logger.debug("@@@@ ExceptionHandler Called @@@@");
509             if (t instanceof MessageRedeliveredException)
510             {
511                 countDown.countDown();
512                 try
513                 {
514                     // MessageRedeliveredException mre =
515                     // (MessageRedeliveredException)t;
516                     Message msg = (Message)message.getPayload();
517 
518                     assertNotNull(msg);
519                     assertTrue(msg.getJMSRedelivered());
520                     assertTrue(msg instanceof TextMessage);
521                     // No need to commit transaction as the Tx template will
522                     // auto matically commit by default
523                     super.handleMessagingException(message, t);
524                 }
525                 catch (Exception e)
526                 {
527                     fail(e.getMessage());
528                 }
529             }
530             else
531             {
532                 logger.error(ExceptionUtils.getFullStackTrace(t));
533                 fail(t.getMessage());
534             }
535             super.handleMessagingException(message, t);
536         }
537 
538         public void handleException(Throwable t)
539         {
540             // TODO is this really supposed to be empty?
541         }
542     }
543 
544     public void testTransactedRedeliveryToDLDestination() throws Exception
545     {
546         if (notExcluded(TRANSACTED_REDELIVERY_TO_DL_DESTINATION))
547         {
548             // there are 2 check points for each message delivered, so
549             // the message will be delivered twice before this countdown will release
550             final int countDownInitialCount = 4;
551             final CountDownLatch countDown = new CountDownLatch(countDownInitialCount);
552 
553             // setup the component and start Mule
554             UMODescriptor descriptor = getDescriptor("testComponent", FunctionalTestComponent.class.getName());
555 
556             EventCallback callback = new EventCallback()
557             {
558                 public synchronized void eventReceived(UMOEventContext context, Object component) throws Exception
559                 {
560                     callbackCalled = true;
561                     currentTx = context.getCurrentTransaction();
562                     assertNotNull(currentTx);
563                     assertTrue(currentTx.isBegun());
564                     logger.debug("@@@@ Rolling back transaction @@@@");
565                     currentTx.setRollbackOnly();
566                     countDown.countDown();
567                 }
568             };
569 
570             initialiseComponent(descriptor, UMOTransactionConfig.ACTION_ALWAYS_BEGIN, callback);
571             UMOManager manager = MuleManager.getInstance();
572             addResultListener(getDLDest().getAddress(), countDown);
573 
574             JmsConnector umoCnn = (JmsConnector)manager.lookupConnector(CONNECTOR_NAME);
575 
576             // After redelivery retry the message and then fail
577             umoCnn.setMaxRedelivery(1);
578 
579             // Set the test Exception strategy
580             umoCnn.setExceptionListener(new RollbackExceptionListener(countDown, getDLDest()));
581 
582             // Start the server
583             manager.start();
584 
585             // Send a test message firstso that it is there when the component is
586             // started
587             send(DEFAULT_MESSAGE, false, Session.AUTO_ACKNOWLEDGE);
588 
589             afterInitialise();
590             countDown.await(LOCK_WAIT, TimeUnit.MILLISECONDS);
591             assertTrue("Only " + (countDownInitialCount - countDown.getCount()) + " of " + countDownInitialCount
592                     + " checkpoints hit", countDown.getCount() == 0);
593 
594             assertNotNull(currentMsg);
595             logger.debug(currentMsg);
596             String dest = currentMsg.getStringProperty(MuleProperties.MULE_ENDPOINT_PROPERTY);
597             assertNotNull(dest);
598             assertEquals(getDLDest().getUri().toString(), dest);
599             assertTrue(callbackCalled);
600 
601             // Make sure the message isn't on the queue
602             assertNull(receive(getInDest().getAddress(), 2000));
603         }
604     }
605 }