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.tck.junit4;
8   
9   import org.mule.MessageExchangePattern;
10  import org.mule.api.MuleContext;
11  import org.mule.api.MuleEvent;
12  import org.mule.api.MuleEventContext;
13  import org.mule.api.MuleException;
14  import org.mule.api.MuleSession;
15  import org.mule.api.config.ConfigurationBuilder;
16  import org.mule.api.config.MuleConfiguration;
17  import org.mule.api.construct.FlowConstruct;
18  import org.mule.api.context.MuleContextBuilder;
19  import org.mule.api.context.MuleContextFactory;
20  import org.mule.api.context.notification.MuleContextNotificationListener;
21  import org.mule.api.endpoint.ImmutableEndpoint;
22  import org.mule.api.endpoint.InboundEndpoint;
23  import org.mule.api.endpoint.OutboundEndpoint;
24  import org.mule.api.processor.MessageProcessor;
25  import org.mule.api.registry.RegistrationException;
26  import org.mule.api.routing.filter.Filter;
27  import org.mule.api.service.Service;
28  import org.mule.api.transformer.Transformer;
29  import org.mule.api.transport.Connector;
30  import org.mule.config.DefaultMuleConfiguration;
31  import org.mule.config.builders.DefaultsConfigurationBuilder;
32  import org.mule.config.builders.SimpleConfigurationBuilder;
33  import org.mule.context.DefaultMuleContextBuilder;
34  import org.mule.context.DefaultMuleContextFactory;
35  import org.mule.context.notification.MuleContextNotification;
36  import org.mule.tck.MuleTestUtils;
37  import org.mule.tck.SensingNullMessageProcessor;
38  import org.mule.tck.TestingWorkListener;
39  import org.mule.tck.TriggerableMessageSource;
40  import org.mule.tck.testmodels.mule.TestConnector;
41  import org.mule.util.ClassUtils;
42  import org.mule.util.FileUtils;
43  import org.mule.util.StringUtils;
44  import org.mule.util.concurrent.Latch;
45  
46  import java.util.ArrayList;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.Properties;
50  import java.util.concurrent.atomic.AtomicReference;
51  
52  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
53  
54  import org.junit.After;
55  import org.junit.AfterClass;
56  import org.junit.Before;
57  
58  /**
59   * Extends {@link AbstractMuleTestCase} providing access to a {@link MuleContext}
60   * instance and tools for manage it.
61   */
62  public abstract class AbstractMuleContextTestCase extends AbstractMuleTestCase
63  {
64  
65      /**
66       * Top-level directories under <code>.mule</code> which are not deleted on each
67       * test case recycle. This is required, e.g. to play nice with transaction manager
68       * recovery service object store.
69       */
70      public static final String[] IGNORED_DOT_MULE_DIRS = new String[]{"transaction-log"};
71  
72      /**
73       * If the annotations module is on the classpath, also enable annotations config builder
74       */
75      public static final String CLASSNAME_ANNOTATIONS_CONFIG_BUILDER = "org.mule.config.AnnotationsConfigurationBuilder";
76  
77      /**
78       * The context used to run this test. Context will be created per class
79       * or per method depending on  {@link #disposeContextPerClass}.
80       * The context will be started only when {@link #startContext} is true.
81       */
82      protected static MuleContext muleContext;
83  
84      /**
85       * Start the muleContext once it's configured (defaults to false for AbstractMuleTestCase, true for FunctionalTestCase).
86       */
87      private boolean startContext = false;
88  
89      /**
90       * Convenient test message for unit testing.
91       */
92      public static final String TEST_MESSAGE = "Test Message";
93  
94      /**
95       * Default timeout for multithreaded tests (using CountDownLatch, WaitableBoolean, etc.),
96       * in milliseconds.  The higher this value, the more reliable the test will be, so it
97       * should be set high for Continuous Integration.  However, this can waste time during
98       * day-to-day development cycles, so you may want to temporarily lower it while debugging.
99       */
100     public static final long LOCK_TIMEOUT = 30000;
101 
102     /**
103      * Default timeout for waiting for responses
104      */
105     public static final int RECEIVE_TIMEOUT = 5000;
106 
107     /**
108      * Use this as a semaphore to the unit test to indicate when a callback has successfully been called.
109      */
110     protected Latch callbackCalled;
111 
112     /**
113      * Indicates if the context should be instantiated per context. Default is
114      * false, which means that a context will be instantiated per test method.
115      */
116     private boolean disposeContextPerClass;
117 
118     protected boolean isDisposeContextPerClass()
119     {
120         return disposeContextPerClass;
121     }
122 
123     protected void setDisposeContextPerClass(boolean val)
124     {
125         disposeContextPerClass = val;
126     }
127 
128     @Before
129     public final void setUpMuleContext() throws Exception
130     {
131         muleContext = createMuleContext();
132 
133         if (isStartContext() && muleContext != null && !muleContext.isStarted())
134         {
135             startMuleContext();
136         }
137 
138         doSetUp();
139     }
140 
141     private void startMuleContext() throws MuleException, InterruptedException
142     {
143         final AtomicReference<Latch> contextStartedLatch = new AtomicReference<Latch>();
144 
145         contextStartedLatch.set(new Latch());
146         muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>()
147         {
148             public void onNotification(MuleContextNotification notification)
149             {
150                 if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED)
151                 {
152                     contextStartedLatch.get().countDown();
153                 }
154             }
155         });
156 
157         muleContext.start();
158 
159         contextStartedLatch.get().await(20, TimeUnit.SECONDS);
160     }
161 
162     /**
163      * Enables the adding of extra behavior on the set up stage of a test right
164      * after the creation of the mule context in {@link #setUpMuleContext}.
165      * <p>
166      * Under normal circumstances this method could be replace with a
167      * <code>@Before</code> annotated method.
168      *
169      * @throws Exception if something fails that should halt the test case
170      */
171     protected void doSetUp() throws Exception
172     {
173     }
174 
175     protected MuleContext createMuleContext() throws Exception
176     {
177         // Should we set up the manager for every method?
178         MuleContext context;
179         if (isDisposeContextPerClass() && muleContext != null)
180         {
181             context = muleContext;
182         }
183         else
184         {
185             MuleContextFactory muleContextFactory = new DefaultMuleContextFactory();
186             List<ConfigurationBuilder> builders = new ArrayList<ConfigurationBuilder>();
187             builders.add(new SimpleConfigurationBuilder(getStartUpProperties()));
188             //If the annotations module is on the classpath, add the annotations config builder to the list
189             //This will enable annotations config for this instance
190             if (ClassUtils.isClassOnPath(CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, getClass()))
191             {
192                 builders.add((ConfigurationBuilder) ClassUtils.instanciateClass(CLASSNAME_ANNOTATIONS_CONFIG_BUILDER,
193                                                                                 ClassUtils.NO_ARGS, getClass()));
194             }
195             builders.add(getBuilder());
196             addBuilders(builders);
197             MuleContextBuilder contextBuilder = new DefaultMuleContextBuilder();
198             configureMuleContext(contextBuilder);
199             context = muleContextFactory.createMuleContext(builders, contextBuilder);
200             if (!isGracefulShutdown())
201             {
202                 ((DefaultMuleConfiguration) context.getConfiguration()).setShutdownTimeout(0);
203             }
204         }
205         return context;
206     }
207 
208     //This sohuldn't be needed by Test cases but can be used by base testcases that wish to add further builders when
209     //creating the MuleContext.
210     protected void addBuilders(List<ConfigurationBuilder> builders)
211     {
212         //No op
213     }
214 
215     /**
216      * Override this method to set properties of the MuleContextBuilder before it is
217      * used to create the MuleContext.
218      */
219     protected void configureMuleContext(MuleContextBuilder contextBuilder)
220     {
221         contextBuilder.setWorkListener(new TestingWorkListener());
222     }
223 
224     protected ConfigurationBuilder getBuilder() throws Exception
225     {
226         return new DefaultsConfigurationBuilder();
227     }
228 
229     protected String getConfigurationResources()
230     {
231         return StringUtils.EMPTY;
232     }
233 
234     protected Properties getStartUpProperties()
235     {
236         return null;
237     }
238 
239     @After
240     public final void disposeContextPerTest() throws Exception
241     {
242         doTearDown();
243 
244         if (!isDisposeContextPerClass())
245         {
246             disposeContext();
247         }
248     }
249 
250     @AfterClass
251     public static void disposeContext()
252     {
253         try
254         {
255             if (muleContext != null && !(muleContext.isDisposed() || muleContext.isDisposing()))
256             {
257                 muleContext.dispose();
258 
259                 MuleConfiguration configuration = muleContext.getConfiguration();
260 
261                 if (configuration != null)
262                 {
263                     final String workingDir = configuration.getWorkingDirectory();
264                     // do not delete TM recovery object store, everything else is good to
265                     // go
266                     FileUtils.deleteTree(FileUtils.newFile(workingDir), IGNORED_DOT_MULE_DIRS);
267                 }
268             }
269             FileUtils.deleteTree(FileUtils.newFile("./ActiveMQ"));
270         }
271         finally
272         {
273             muleContext = null;
274         }
275     }
276 
277     /**
278      * Enables the adding of extra behavior on the tear down stage of a test
279      * before the mule context is disposed in {@link #disposeContextPerTest}.
280      * <p>
281      * Under normal circumstances this method could be replace with a
282      * <code>@After</code> annotated method.
283      *
284      * @throws Exception if something fails that should halt the testcase
285      */
286     protected void doTearDown() throws Exception
287     {
288     }
289 
290     public static InboundEndpoint getTestInboundEndpoint(String name) throws Exception
291     {
292         return MuleTestUtils.getTestInboundEndpoint(name, muleContext);
293     }
294 
295     public static OutboundEndpoint getTestOutboundEndpoint(String name) throws Exception
296     {
297         return MuleTestUtils.getTestOutboundEndpoint(name, muleContext);
298     }
299 
300     public static InboundEndpoint getTestInboundEndpoint(MessageExchangePattern mep) throws Exception
301     {
302         return MuleTestUtils.getTestInboundEndpoint(mep, muleContext);
303     }
304 
305     public static InboundEndpoint getTestTransactedInboundEndpoint(MessageExchangePattern mep) throws Exception
306     {
307         return MuleTestUtils.getTestTransactedInboundEndpoint(mep, muleContext);
308     }
309 
310     public static InboundEndpoint getTestInboundEndpoint(String name, String uri) throws Exception
311     {
312         return MuleTestUtils.getTestInboundEndpoint(name, muleContext, uri, null, null, null, null);
313     }
314 
315     public static OutboundEndpoint getTestOutboundEndpoint(String name, String uri) throws Exception
316     {
317         return MuleTestUtils.getTestOutboundEndpoint(name, muleContext, uri, null, null, null);
318     }
319 
320     public static InboundEndpoint getTestInboundEndpoint(String name, List<Transformer> transformers) throws Exception
321     {
322         return MuleTestUtils.getTestInboundEndpoint(name, muleContext, null, transformers, null, null, null);
323     }
324 
325     public static OutboundEndpoint getTestOutboundEndpoint(String name, List<Transformer> transformers) throws Exception
326     {
327         return MuleTestUtils.getTestOutboundEndpoint(name, muleContext, null, transformers, null, null);
328     }
329 
330     public static InboundEndpoint getTestInboundEndpoint(String name, String uri,
331                                                          List<Transformer> transformers, Filter filter, Map<Object, Object> properties, Connector connector) throws Exception
332     {
333         return MuleTestUtils.getTestInboundEndpoint(name, muleContext, uri, transformers, filter, properties, connector);
334     }
335 
336     public static OutboundEndpoint getTestOutboundEndpoint(String name, String uri,
337                                                            List<Transformer> transformers, Filter filter, Map<Object, Object> properties) throws Exception
338     {
339         return MuleTestUtils.getTestOutboundEndpoint(name, muleContext, uri, transformers, filter, properties);
340     }
341 
342     public static OutboundEndpoint getTestOutboundEndpoint(String name, String uri,
343                                                            List<Transformer> transformers, Filter filter, Map<Object, Object> properties, Connector connector) throws Exception
344     {
345         return MuleTestUtils.getTestOutboundEndpoint(name, muleContext, uri, transformers, filter, properties, connector);
346     }
347 
348     public static MuleEvent getTestEvent(Object data, FlowConstruct service) throws Exception
349     {
350         return MuleTestUtils.getTestEvent(data, service, MessageExchangePattern.REQUEST_RESPONSE, muleContext);
351     }
352 
353     public static MuleEvent getTestEvent(Object data, FlowConstruct service, MessageExchangePattern mep) throws Exception
354     {
355         return MuleTestUtils.getTestEvent(data, service, mep, muleContext);
356     }
357 
358     public static MuleEvent getTestEvent(Object data) throws Exception
359     {
360         return MuleTestUtils.getTestEvent(data, MessageExchangePattern.REQUEST_RESPONSE, muleContext);
361     }
362 
363     public static MuleEvent getTestEventUsingFlow(Object data) throws Exception
364     {
365         return MuleTestUtils.getTestEventUsingFlow(data, MessageExchangePattern.REQUEST_RESPONSE, muleContext);
366     }
367 
368     public static MuleEvent getTestEvent(Object data, MessageExchangePattern mep) throws Exception
369     {
370         return MuleTestUtils.getTestEvent(data, mep, muleContext);
371     }
372 
373     public static MuleEvent getTestInboundEvent(Object data, MuleSession session) throws Exception
374     {
375         return MuleTestUtils.getTestInboundEvent(data, session, muleContext);
376     }
377 
378     public static MuleEvent getTestInboundEvent(Object data) throws Exception
379     {
380         return MuleTestUtils.getTestInboundEvent(data, MessageExchangePattern.REQUEST_RESPONSE, muleContext);
381     }
382 
383     public static MuleEvent getTestInboundEvent(Object data, MessageExchangePattern mep) throws Exception
384     {
385         return MuleTestUtils.getTestInboundEvent(data, mep, muleContext);
386     }
387 
388     public static MuleEventContext getTestEventContext(Object data) throws Exception
389     {
390         return MuleTestUtils.getTestEventContext(data, MessageExchangePattern.REQUEST_RESPONSE, muleContext);
391     }
392 
393     public static MuleEventContext getTestEventContext(Object data, MessageExchangePattern mep) throws Exception
394     {
395         return MuleTestUtils.getTestEventContext(data, mep, muleContext);
396     }
397 
398     public static Transformer getTestTransformer() throws Exception
399     {
400         return MuleTestUtils.getTestTransformer();
401     }
402 
403     public static MuleEvent getTestEvent(Object data, ImmutableEndpoint endpoint) throws Exception
404     {
405         return MuleTestUtils.getTestEvent(data, endpoint, muleContext);
406     }
407 
408     public static MuleEvent getTestEvent(Object data, Service service, ImmutableEndpoint endpoint)
409             throws Exception
410     {
411         return MuleTestUtils.getTestEvent(data, service, endpoint, muleContext);
412     }
413 
414     public static MuleSession getTestSession(Service service, MuleContext context)
415     {
416         return MuleTestUtils.getTestSession(service, context);
417     }
418 
419     public static TestConnector getTestConnector() throws Exception
420     {
421         return MuleTestUtils.getTestConnector(muleContext);
422     }
423 
424     public static Service getTestService() throws Exception
425     {
426         return MuleTestUtils.getTestService(muleContext);
427     }
428 
429     public static Service getTestService(String name, Class<?> clazz) throws Exception
430     {
431         return MuleTestUtils.getTestService(name, clazz, muleContext);
432     }
433 
434     public static Service getTestService(String name, Class<?> clazz, Map<?, ?> props) throws Exception
435     {
436         return MuleTestUtils.getTestService(name, clazz, props, muleContext);
437     }
438 
439     protected boolean isStartContext()
440     {
441         return startContext;
442     }
443 
444     protected void setStartContext(boolean startContext)
445     {
446         this.startContext = startContext;
447     }
448 
449     /**
450      * Determines if the test case should perform graceful shutdown or not.
451      * Default is false so that tests run more quickly.
452      *
453      * @return
454      */
455     protected boolean isGracefulShutdown()
456     {
457         return false;
458     }
459 
460     /**
461      * Create an object of instance <code>clazz</code>. It will then register the object with the registry so that any
462      * dependencies are injected and then the object will be initialised.
463      * Note that if the object needs to be configured with additional state that cannot be passed into the constructor you should
464      * create an instance first set any additional data on the object then call {@link #initialiseObject(Object)}.
465      *
466      * @param clazz the class to create an instance of.
467      * @param <T>   Object of this type will be returned
468      * @return an initialised instance of <code>class</code>
469      * @throws Exception if there is a problem creating or initializing the object
470      */
471     protected <T extends Object> T createObject(Class<T> clazz) throws Exception
472     {
473         return createObject(clazz, ClassUtils.NO_ARGS);
474     }
475 
476     /**
477      * Create an object of instance <code>clazz</code>. It will then register the object with the registry so that any
478      * dependencies are injected and then the object will be initialised.
479      * Note that if the object needs to be configured with additional state that cannot be passed into the constructor you should
480      * create an instance first set any additional data on the object then call {@link #initialiseObject(Object)}.
481      *
482      * @param clazz the class to create an instance of.
483      * @param args  constructor parameters
484      * @param <T>   Object of this type will be returned
485      * @return an initialised instance of <code>class</code>
486      * @throws Exception if there is a problem creating or initializing the object
487      */
488     @SuppressWarnings("unchecked")
489     protected <T extends Object> T createObject(Class<T> clazz, Object... args) throws Exception
490     {
491         if (args == null)
492         {
493             args = ClassUtils.NO_ARGS;
494         }
495         Object o = ClassUtils.instanciateClass(clazz, args);
496         muleContext.getRegistry().registerObject(String.valueOf(o.hashCode()), o);
497         return (T) o;
498     }
499 
500     /**
501      * A convenience method that will register an object in the registry using its hashcode as the key.  This will cause the object
502      * to have any objects injected and lifecycle methods called.  Note that the object lifecycle will be called to the same current
503      * lifecycle as the MuleContext
504      *
505      * @param o the object to register and initialise it
506      * @throws org.mule.api.registry.RegistrationException
507      */
508     protected void initialiseObject(Object o) throws RegistrationException
509     {
510         muleContext.getRegistry().registerObject(String.valueOf(o.hashCode()), o);
511     }
512 
513     public SensingNullMessageProcessor getSensingNullMessageProcessor()
514     {
515         return new SensingNullMessageProcessor();
516     }
517 
518     public TriggerableMessageSource getTriggerableMessageSource(MessageProcessor listener)
519     {
520         return new TriggerableMessageSource(listener);
521     }
522 
523     public TriggerableMessageSource getTriggerableMessageSource()
524     {
525         return new TriggerableMessageSource();
526     }
527 }