View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.transport;
8   
9   import org.mule.api.MuleContext;
10  import org.mule.api.MuleException;
11  import org.mule.api.context.WorkManager;
12  import org.mule.api.endpoint.ImmutableEndpoint;
13  import org.mule.api.retry.RetryCallback;
14  import org.mule.api.retry.RetryPolicyTemplate;
15  import org.mule.endpoint.MuleEndpointURI;
16  import org.mule.transport.MuleAbstractTransportMessageHandlerTestCase.MethodInvocation.MethodPart;
17  
18  import java.util.List;
19  import java.util.Vector;
20  import java.util.concurrent.atomic.AtomicBoolean;
21  
22  import edu.umd.cs.mtc.MultithreadedTestCase;
23  import edu.umd.cs.mtc.TestFramework;
24  
25  import org.apache.commons.lang.builder.EqualsBuilder;
26  import org.apache.commons.lang.builder.HashCodeBuilder;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.junit.After;
30  import org.junit.Before;
31  import org.junit.Ignore;
32  import org.junit.Test;
33  import org.mockito.invocation.InvocationOnMock;
34  import org.mockito.stubbing.Answer;
35  
36  import static org.junit.Assert.fail;
37  import static org.mockito.Matchers.any;
38  import static org.mockito.Mockito.mock;
39  import static org.mockito.Mockito.when;
40  
41  /**
42   * This test tests class {@link AbstractTransportMessageHandler} but its name starts with "Mule"
43   * because there is an exclusion rule in parent pom for test classes that have their
44   * names starting with "Abstract".
45   */
46  public class MuleAbstractTransportMessageHandlerTestCase
47  {
48      /**
49       * The logger used for this class
50       */
51      static Log log = LogFactory.getLog(MuleAbstractTransportMessageHandlerTestCase.class);
52  
53      @Test
54      public void testStartRethrowsMuleExceptionCorrectly() throws Exception
55      {
56          final MuleException someMuleException = mock(MuleException.class);
57          AbstractTransportMessageHandler connectable = new AbstractTransportMessageHandler(createDummyEndpoint())
58          {
59              @Override
60              protected void doStart() throws MuleException
61              {
62                  throw someMuleException;
63              }
64  
65              @Override
66              protected WorkManager getWorkManager()
67              {
68                  return null;
69              }
70  
71              @Override
72              protected ConnectableLifecycleManager createLifecycleManager()
73              {
74                  return new ConnectableLifecycleManager("test", this);
75              }
76          };
77          connectable.initialise();
78          connectable.connect();
79          try
80          {
81              connectable.start();
82              fail("Should have thrown a " + MuleException.class.getSimpleName());
83          }
84          catch (MuleException caughtException)
85          {
86              assertExceptionIsInCaughtException(someMuleException, caughtException);
87          }
88      }
89  
90      /**
91       * This test tests that start method is thread safe and that
92       * {@link AbstractTransportMessageHandler#doStart()} is always called making sure that the
93       * connectable is {@link AbstractTransportMessageHandler#isConnected() connected}.
94       * <p>
95       * To make multithreaded test easier it uses a library called <a
96       * href="http://www.cs.umd.edu/projects/PL/multithreadedtc/overview.html"
97       * >MultithreadedTC</a>. You will perhaps go and read that link if you want to
98       * understand how this test works.
99       * 
100      * @throws Throwable
101      */
102     @Ignore
103     @Test
104     public void testStartIsThreadSafe() throws Throwable
105     {
106         TestFramework.runOnce(new AbstractConnectableMultithreaded());
107     }
108 
109     /**
110      * This inner class will do the work of
111      * {@link MuleAbstractTransportMessageHandlerTestCase#testStartIsThreadSafe()
112      * testStartIsThreadSafe()}.
113      */
114     @Ignore
115     class AbstractConnectableMultithreaded extends MultithreadedTestCase
116     {
117         private volatile AbstractConnectableForTest connectable;
118 
119         /**
120          * This is called before any of the thread* methods are called. Pretty much
121          * like a {@link Before} method in JUnit 4.
122          */
123         @Override
124         public void initialize()
125         {
126             try
127             {
128                 ImmutableEndpoint endpoint = createDummyEndpoint();
129 
130                 connectable = new AbstractConnectableForTest(endpoint);
131                 connectable.initialise();
132             }
133             catch (Exception e)
134             {
135                 throw new RuntimeException(e);
136             }
137         }
138 
139         /**
140          * This will be called by the <a
141          * href="http://www.cs.umd.edu/projects/PL/multithreadedtc/overview.html"
142          * >MultithreadedTC</a> framework with an independent thread.
143          * 
144          * @throws Exception on unexpected error.
145          */
146         public void thread1() throws Exception
147         {
148             connectable.start();
149         }
150 
151         /**
152          * @see #thread1()
153          * @throws Exception on unexpected error
154          */
155         public void thread2() throws Exception
156         {
157             // waiting for first tick means that thread1 will work on tick 0 and
158             // after it blocks then this one will start.
159             waitForTick(1);
160             connectable.start();
161         }
162 
163         /**
164          * This is called after all the thread* methods finish. Pretty much like an
165          * {@link After} method in JUnit 4.
166          */
167         @Override
168         public void finish()
169         {
170             for (MethodInvocation methodInvocation : connectable.methodInvocations)
171             {
172                 log.debug(methodInvocation);
173             }
174 
175             int i = 0;
176             assertEquals("doConnect", connectable.methodInvocations.get(i++).getMethodName());
177             assertEquals("doConnect", connectable.methodInvocations.get(i++).getMethodName());
178             assertEquals("doStart", connectable.methodInvocations.get(i++).getMethodName());
179             assertEquals("doStart", connectable.methodInvocations.get(i++).getMethodName());
180         }
181 
182         /**
183          * This is a specific implementation of {@link AbstractTransportMessageHandler} for this
184          * test that will only implement {@link #doConnect()} and {@link #doStart()}
185          * methods.
186          * <p>
187          * The implementation of those methods simulate a long {@link #doConnect()}
188          * execution that makes the other thread attempt to execute
189          * {@link #doStart()}. They have also code that validates that
190          * {@link #doStart()} is never called before {@link #doConnect()} has
191          * finished.
192          */
193         @Ignore
194         class AbstractConnectableForTest extends AbstractTransportMessageHandler
195         {
196             private final AtomicBoolean doConnectCalled = new AtomicBoolean();
197             private final AtomicBoolean doStartCalled = new AtomicBoolean();
198 
199             /**
200              * This list will hold reference to each of the calls to the methods
201              * {@link #doConnect()} and {@link #doStart()}. We use a {@link Vector}
202              * because it will be accessed by different threads.
203              */
204             List<MethodInvocation> methodInvocations = new Vector<MethodInvocation>();
205 
206             public AbstractConnectableForTest(ImmutableEndpoint endpoint)
207             {
208                 super(endpoint);
209             }
210 
211             @Override
212             protected WorkManager getWorkManager()
213             {
214                 return null;
215             }
216 
217             @Override
218             protected ConnectableLifecycleManager createLifecycleManager()
219             {
220                 return new ConnectableLifecycleManager("test", this);
221             }
222 
223             @Override
224             protected void doConnect() throws Exception
225             {
226                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doConnect",
227                     MethodPart.BEGINNING));
228                 assertTrue(doConnectCalled.compareAndSet(false, true));
229                 assertFalse(doStartCalled.get());
230 
231                 // This method is called by thread1 in the test and waiting
232                 // for tick 2 will make thread 2 execute during tick 1, attempting
233                 // to call doStart(). Thread 2 should be then blocked waiting on a
234                 // monitor until AbstractConnectable.connected is true, which
235                 // triggers tick 2 and thread 1 will resume its execution,
236                 // finishing the exection of this method.
237                 waitForTick(2);
238 
239                 assertFalse(doStartCalled.get());
240 
241                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doConnect",
242                     MethodPart.END));
243             }
244 
245             @Override
246             protected void doStart() throws MuleException
247             {
248                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doStart",
249                     MethodPart.BEGINNING));
250                 assertTrue(doStartCalled.compareAndSet(false, true));
251                 assertTrue(doConnectCalled.get());
252                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doStart", MethodPart.END));
253             }
254 
255         }
256     }
257 
258     /**
259      * This class just represent a method invocation that allow keeping track of the
260      * order in which calls are made by different threads.
261      */
262     @org.junit.Ignore
263     static class MethodInvocation
264     {
265         @Ignore
266         static enum MethodPart
267         {
268             BEGINNING, END
269         }
270 
271         private final Thread thread;
272         private final String methodName;
273         private final MethodPart methodPart;
274 
275         public MethodInvocation(Thread thread, String methodName, MethodPart methodPart)
276         {
277             this.thread = thread;
278             this.methodName = methodName;
279             this.methodPart = methodPart;
280         }
281 
282         public Thread getThread()
283         {
284             return thread;
285         }
286 
287         public String getMethodName()
288         {
289             return methodName;
290         }
291 
292         public MethodPart getMethodPart()
293         {
294             return methodPart;
295         }
296 
297         @Override
298         public boolean equals(Object obj)
299         {
300             if (this == obj)
301             {
302                 return true;
303             }
304             else if (obj == null || obj.getClass() != this.getClass())
305             {
306                 return false;
307             }
308             else
309             {
310                 MethodInvocation other = (MethodInvocation) obj;
311                 return new EqualsBuilder().append(this.thread, other.thread).append(this.methodName,
312                     other.methodName).append(this.methodPart, other.methodPart).isEquals();
313             }
314         }
315 
316         @Override
317         public int hashCode()
318         {
319             return new HashCodeBuilder().append(this.thread)
320                 .append(this.methodName)
321                 .append(this.methodPart)
322                 .toHashCode();
323         }
324 
325         @Override
326         public String toString()
327         {
328             return "Thread " + this.thread + " passing through " + this.methodName + "() at the "
329                    + this.methodPart;
330         }
331     }
332 
333     private void assertExceptionIsInCaughtException(MuleException someMuleException, MuleException caughtException)
334     {
335         boolean found = false;
336         Throwable candidate = caughtException;
337         while (candidate != null)
338         {
339             if (someMuleException.equals(candidate))
340             {
341                 found = true;
342                 break;
343             }
344             
345             candidate = candidate.getCause();
346         }
347         
348         if (found == false)
349         {
350             fail();
351         }
352     }
353     
354     /**
355      * @return an dummy implementation of {@link ImmutableEndpoint} suitable for this
356      *         test.
357      * @throws Exception
358      */
359     ImmutableEndpoint createDummyEndpoint() throws Exception
360     {
361         ImmutableEndpoint endpoint = mock(ImmutableEndpoint.class);
362         MuleContext muleContext = mock(MuleContext.class);
363         when(endpoint.getEndpointURI()).thenReturn(new MuleEndpointURI("http://dummy.endpoint/", muleContext));
364         AbstractConnector connector = mock(AbstractConnector.class);
365         when(endpoint.getConnector()).thenReturn(connector);
366 
367         RetryPolicyTemplate retryPolicyTemplate = mock(RetryPolicyTemplate.class);
368         when(endpoint.getRetryPolicyTemplate()).thenReturn(retryPolicyTemplate);
369         when(retryPolicyTemplate.execute(any(RetryCallback.class), any(WorkManager.class))).thenAnswer(
370             new Answer<Object>()
371             {
372                 public Object answer(InvocationOnMock invocation) throws Throwable
373                 {
374                     RetryCallback retryCallback = (RetryCallback) invocation.getArguments()[0];
375                     retryCallback.doWork(null);
376                     return null;
377                 }
378             });
379 
380         return endpoint;
381     }
382 }