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.processor;
8   
9   import static org.junit.Assert.assertEquals;
10  import static org.junit.Assert.assertNotNull;
11  import static org.junit.Assert.assertTrue;
12  import static org.mockito.Matchers.any;
13  import static org.mockito.Matchers.argThat;
14  import static org.mockito.Mockito.mock;
15  import static org.mockito.Mockito.timeout;
16  import static org.mockito.Mockito.verify;
17  import static org.mockito.Mockito.when;
18  
19  import org.mule.MessageExchangePattern;
20  import org.mule.api.MuleEvent;
21  import org.mule.api.MuleException;
22  import org.mule.api.MuleRuntimeException;
23  import org.mule.api.config.ThreadingProfile;
24  import org.mule.api.exception.MessagingExceptionHandler;
25  import org.mule.api.lifecycle.Initialisable;
26  import org.mule.api.lifecycle.InitialisationException;
27  import org.mule.api.lifecycle.Lifecycle;
28  import org.mule.api.lifecycle.LifecycleState;
29  import org.mule.api.lifecycle.Startable;
30  import org.mule.api.lifecycle.Stoppable;
31  import org.mule.api.processor.MessageProcessor;
32  import org.mule.config.ChainedThreadingProfile;
33  import org.mule.config.QueueProfile;
34  import org.mule.construct.SimpleFlowConstruct;
35  import org.mule.management.stats.QueueStatistics;
36  import org.mule.service.Pausable;
37  import org.mule.util.concurrent.Latch;
38  
39  import java.beans.ExceptionListener;
40  
41  import javax.resource.spi.work.Work;
42  import javax.resource.spi.work.WorkEvent;
43  import javax.resource.spi.work.WorkException;
44  
45  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
46  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
47  
48  import org.junit.Test;
49  import org.mockito.ArgumentMatcher;
50  import org.mockito.invocation.InvocationOnMock;
51  import org.mockito.stubbing.Answer;
52  
53  public class SedaStageInterceptingMessageProcessorTestCase extends
54      OptionalAsyncInterceptingMessageProcessorTestCase implements ExceptionListener
55  {
56  
57      QueueProfile queueProfile = new QueueProfile();
58      int queueTimeout;
59      QueueStatistics queueStatistics;
60      TestLifeCycleState lifeCycleState;
61  
62      @Override
63      protected void doSetUp() throws Exception
64      {
65          queueStatistics = new TestQueueStatistics();
66          queueTimeout = muleContext.getConfiguration().getDefaultQueueTimeout();
67          lifeCycleState = new TestLifeCycleState();
68          super.doSetUp();
69          ((Initialisable) messageProcessor).initialise();
70          ((Startable) messageProcessor).start();
71          lifeCycleState.start();
72      }
73  
74      @Override
75      protected boolean isStartContext()
76      {
77          return false;
78      }
79  
80      @Override
81      protected void doTearDown() throws Exception
82      {
83          super.doTearDown();
84          ((Stoppable) messageProcessor).stop();
85          lifeCycleState.stop();
86          lifeCycleState.dispose();
87  
88      }
89  
90      @Test
91      public void testProcessOneWayThreadWaitTimeout() throws Exception
92      {
93          final int threadTimeout = 20;
94          ThreadingProfile threadingProfile = new ChainedThreadingProfile(
95              muleContext.getDefaultThreadingProfile());
96          threadingProfile.setThreadWaitTimeout(threadTimeout);
97          // Need 3 threads: 1 for polling, 2 to process work successfully without timeout
98          threadingProfile.setMaxThreadsActive(3);
99          threadingProfile.setPoolExhaustedAction(ThreadingProfile.WHEN_EXHAUSTED_WAIT);
100         threadingProfile.setMuleContext(muleContext);
101 
102         MessageProcessor mockListener = mock(MessageProcessor.class);
103         when(mockListener.process((MuleEvent) any())).thenAnswer(new Answer<MuleEvent>()
104         {
105             public MuleEvent answer(InvocationOnMock invocation) throws Throwable
106             {
107                 Thread.sleep(threadTimeout * 2);
108                 return (MuleEvent) invocation.getArguments()[0];
109             }
110         });
111 
112         SedaStageInterceptingMessageProcessor sedaStageInterceptingMessageProcessor = new SedaStageInterceptingMessageProcessor(
113             "testProcessOneWayThreadWaitTimeout", queueProfile, queueTimeout, threadingProfile,
114             queueStatistics, muleContext);
115         sedaStageInterceptingMessageProcessor.setListener(mockListener);
116         sedaStageInterceptingMessageProcessor.initialise();
117         sedaStageInterceptingMessageProcessor.start();
118 
119         MessagingExceptionHandler exceptionHandler = mock(MessagingExceptionHandler.class);
120         SimpleFlowConstruct flow = mock(SimpleFlowConstruct.class);
121         when(flow.getExceptionListener()).thenReturn(exceptionHandler);
122         final MuleEvent event = getTestEvent(TEST_MESSAGE, flow, MessageExchangePattern.ONE_WAY);
123 
124         for (int i = 0; i < 3; i++)
125         {
126             sedaStageInterceptingMessageProcessor.process(event);
127         }
128 
129         ArgumentMatcher<MuleEvent> notSameEvent = new ArgumentMatcher<MuleEvent>()
130         {
131             @Override
132             public boolean matches(Object argument)
133             {
134                 return !argument.equals(event);
135             }
136         };
137 
138         // Two events are processed
139         verify(mockListener, timeout(RECEIVE_TIMEOUT).times(2)).process(argThat(notSameEvent));
140 
141         // One event gets processed by the exception strategy
142         verify(exceptionHandler, timeout(RECEIVE_TIMEOUT).times(1)).handleException((Exception) any(),
143             argThat(notSameEvent));
144 
145     }
146 
147     @Test
148     public void testProcessOneWayWithException() throws Exception
149     {
150         final Latch latch = new Latch();
151         ThreadingProfile threadingProfile = new ChainedThreadingProfile(
152             muleContext.getDefaultThreadingProfile());
153         threadingProfile.setMuleContext(muleContext);
154 
155         MessageProcessor mockListener = mock(MessageProcessor.class);
156         when(mockListener.process((MuleEvent) any())).thenAnswer(new Answer<MuleEvent>()
157         {
158             public MuleEvent answer(InvocationOnMock invocation) throws Throwable
159             {
160                 latch.countDown();
161                 throw new RuntimeException();
162             }
163         });
164 
165         SedaStageInterceptingMessageProcessor sedaStageInterceptingMessageProcessor = new SedaStageInterceptingMessageProcessor(
166             "testProcessOneWayWithException", queueProfile, queueTimeout, threadingProfile, queueStatistics,
167             muleContext);
168         sedaStageInterceptingMessageProcessor.setListener(mockListener);
169         sedaStageInterceptingMessageProcessor.initialise();
170         sedaStageInterceptingMessageProcessor.start();
171 
172         MessagingExceptionHandler exceptionHandler = mock(MessagingExceptionHandler.class);
173         SimpleFlowConstruct flow = mock(SimpleFlowConstruct.class);
174         when(flow.getExceptionListener()).thenReturn(exceptionHandler);
175         final MuleEvent event = getTestEvent(TEST_MESSAGE, flow, MessageExchangePattern.ONE_WAY);
176 
177         sedaStageInterceptingMessageProcessor.process(event);
178 
179         assertTrue(latch.await(RECEIVE_TIMEOUT, TimeUnit.MILLISECONDS));
180 
181         ArgumentMatcher<MuleEvent> notSameEvent = new ArgumentMatcher<MuleEvent>()
182         {
183             @Override
184             public boolean matches(Object argument)
185             {
186                 return !argument.equals(event);
187             }
188         };
189 
190         // One event get processed but then throws an exception
191         verify(mockListener, timeout(RECEIVE_TIMEOUT).times(1)).process(argThat(notSameEvent));
192 
193         // One event gets processed by the exception strategy
194         verify(exceptionHandler, timeout(RECEIVE_TIMEOUT).times(1)).handleException((Exception) any(),
195             argThat(notSameEvent));
196 
197     }
198 
199     @Test(expected = RuntimeException.class)
200     public void testProcessOneWayNoThreadingWithException() throws Exception
201     {
202         ThreadingProfile threadingProfile = new ChainedThreadingProfile(
203             muleContext.getDefaultThreadingProfile());
204         threadingProfile.setDoThreading(false);
205         threadingProfile.setMuleContext(muleContext);
206 
207         MessageProcessor mockListener = mock(MessageProcessor.class);
208         when(mockListener.process((MuleEvent) any())).thenThrow(new RuntimeException());
209 
210         SedaStageInterceptingMessageProcessor sedaStageInterceptingMessageProcessor = new SedaStageInterceptingMessageProcessor(
211             "testProcessOneWayNoThreadingWithException", queueProfile, queueTimeout, threadingProfile,
212             queueStatistics, muleContext);
213         sedaStageInterceptingMessageProcessor.setListener(mockListener);
214         sedaStageInterceptingMessageProcessor.initialise();
215         sedaStageInterceptingMessageProcessor.start();
216 
217         MessagingExceptionHandler exceptionHandler = mock(MessagingExceptionHandler.class);
218         SimpleFlowConstruct flow = mock(SimpleFlowConstruct.class);
219         when(flow.getExceptionListener()).thenReturn(exceptionHandler);
220         MuleEvent event = getTestEvent(TEST_MESSAGE, flow, MessageExchangePattern.ONE_WAY);
221 
222         sedaStageInterceptingMessageProcessor.process(event);
223     }
224 
225     protected AsyncInterceptingMessageProcessor createAsyncInterceptingMessageProcessor(MessageProcessor listener)
226         throws Exception
227     {
228         SedaStageInterceptingMessageProcessor mp = new SedaStageInterceptingMessageProcessor("name",
229             queueProfile, queueTimeout, muleContext.getDefaultThreadingProfile(), queueStatistics,
230             muleContext);
231         mp.setListener(listener);
232         return mp;
233     }
234 
235     @Test
236     public void testSpiWorkThrowableHandling() throws Exception
237     {
238         try
239         {
240             new AsyncWorkListener(getSensingNullMessageProcessor()).handleWorkException(getTestWorkEvent(),
241                 "workRejected");
242         }
243         catch (MuleRuntimeException mrex)
244         {
245             assertNotNull(mrex);
246             assertTrue(mrex.getCause().getClass() == Throwable.class);
247             assertEquals("testThrowable", mrex.getCause().getMessage());
248         }
249     }
250 
251     private WorkEvent getTestWorkEvent()
252     {
253         return new WorkEvent(this, // source
254             WorkEvent.WORK_REJECTED, getTestWork(), new WorkException(new Throwable("testThrowable")));
255     }
256 
257     private Work getTestWork()
258     {
259         return new Work()
260         {
261             public void release()
262             {
263                 // noop
264             }
265 
266             public void run()
267             {
268                 // noop
269             }
270         };
271     }
272 
273     class TestQueueStatistics implements QueueStatistics
274     {
275         int incCount;
276         int decCount;
277 
278         public void decQueuedEvent()
279         {
280             decCount++;
281         }
282 
283         public void incQueuedEvent()
284         {
285             incCount++;
286         }
287 
288         public boolean isEnabled()
289         {
290             return true;
291         }
292     }
293 
294     class TestLifeCycleState implements LifecycleState, Lifecycle
295     {
296 
297         AtomicBoolean started = new AtomicBoolean(false);
298         AtomicBoolean stopped = new AtomicBoolean(true);
299         AtomicBoolean disposed = new AtomicBoolean(false);
300         AtomicBoolean initialised = new AtomicBoolean(false);
301         AtomicBoolean paused = new AtomicBoolean(false);
302 
303         public boolean isDisposed()
304         {
305             return disposed.get();
306         }
307 
308         public boolean isDisposing()
309         {
310             return false;
311         }
312 
313         public boolean isInitialised()
314         {
315             return initialised.get();
316         }
317 
318         public boolean isInitialising()
319         {
320             return false;
321         }
322 
323         public boolean isPhaseComplete(String phase)
324         {
325             if (Pausable.PHASE_NAME.equals(phase))
326             {
327                 return paused.get();
328             }
329             else
330             {
331                 return false;
332             }
333         }
334 
335         public boolean isPhaseExecuting(String phase)
336         {
337             return false;
338         }
339 
340         public boolean isStarted()
341         {
342             return started.get();
343         }
344 
345         public boolean isStarting()
346         {
347             return false;
348         }
349 
350         public boolean isStopped()
351         {
352             return stopped.get();
353         }
354 
355         public boolean isStopping()
356         {
357             return false;
358         }
359 
360         public void initialise() throws InitialisationException
361         {
362             initialised.set(true);
363         }
364 
365         public void start() throws MuleException
366         {
367             initialised.set(false);
368             stopped.set(false);
369             started.set(true);
370         }
371 
372         public void stop() throws MuleException
373         {
374             started.set(false);
375             stopped.set(true);
376         }
377 
378         public void dispose()
379         {
380             stopped.set(true);
381             disposed.set(true);
382         }
383 
384         public boolean isValidTransition(String phase)
385         {
386             return false;
387         }
388     }
389 
390 }