001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.testng;
018    
019    import java.io.InputStream;
020    import java.util.Hashtable;
021    import java.util.Map;
022    import java.util.Properties;
023    import java.util.concurrent.TimeUnit;
024    import java.util.concurrent.atomic.AtomicBoolean;
025    import javax.naming.Context;
026    import javax.naming.InitialContext;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.ConsumerTemplate;
030    import org.apache.camel.Endpoint;
031    import org.apache.camel.Exchange;
032    import org.apache.camel.Expression;
033    import org.apache.camel.Message;
034    import org.apache.camel.Predicate;
035    import org.apache.camel.Processor;
036    import org.apache.camel.ProducerTemplate;
037    import org.apache.camel.Service;
038    import org.apache.camel.builder.RouteBuilder;
039    import org.apache.camel.component.mock.MockEndpoint;
040    import org.apache.camel.impl.BreakpointSupport;
041    import org.apache.camel.impl.DefaultCamelContext;
042    import org.apache.camel.impl.DefaultDebugger;
043    import org.apache.camel.impl.InterceptSendToMockEndpointStrategy;
044    import org.apache.camel.impl.JndiRegistry;
045    import org.apache.camel.management.JmxSystemPropertyKeys;
046    import org.apache.camel.model.ProcessorDefinition;
047    import org.apache.camel.spi.Language;
048    import org.apache.camel.spring.CamelBeanPostProcessor;
049    import org.apache.camel.util.StopWatch;
050    import org.apache.camel.util.TimeUtils;
051    import org.slf4j.Logger;
052    import org.slf4j.LoggerFactory;
053    import org.testng.annotations.AfterClass;
054    import org.testng.annotations.AfterMethod;
055    import org.testng.annotations.BeforeMethod;
056    
057    /**
058     * A useful base class which creates a {@link org.apache.camel.CamelContext} with some routes
059     * along with a {@link org.apache.camel.ProducerTemplate} for use in the test case
060     *
061     * @version $Revision: 1172870 $
062     */
063    public abstract class CamelTestSupport extends TestSupport {
064        private static final Logger LOG = LoggerFactory.getLogger(TestSupport.class);
065        private static final ThreadLocal<Boolean> INIT = new ThreadLocal<Boolean>();
066    
067    
068        private static ThreadLocal<CamelContext> threadCamelContext
069            = new ThreadLocal<CamelContext>();
070        private static ThreadLocal<ProducerTemplate> threadTemplate
071            = new ThreadLocal<ProducerTemplate>();
072        private static ThreadLocal<ConsumerTemplate> threadConsumer
073            = new ThreadLocal<ConsumerTemplate>();
074        private static ThreadLocal<Service> threadService
075            = new ThreadLocal<Service>();
076    
077        protected volatile CamelContext context;
078        protected volatile ProducerTemplate template;
079        protected volatile ConsumerTemplate consumer;
080        protected volatile Service camelContextService;
081    
082    
083        private boolean useRouteBuilder = true;
084        private final DebugBreakpoint breakpoint = new DebugBreakpoint();
085        private final StopWatch watch = new StopWatch();
086    
087         /**
088         * Use the RouteBuilder or not
089         * @return <tt>true</tt> then {@link CamelContext} will be auto started,
090         *        <tt>false</tt> then {@link CamelContext} will <b>not</b> be auto started (you will have to start it manually)
091         */
092        public boolean isUseRouteBuilder() {
093            return useRouteBuilder;
094        }
095    
096        public void setUseRouteBuilder(boolean useRouteBuilder) {
097            this.useRouteBuilder = useRouteBuilder;
098        }
099    
100        /**
101         * Override to control whether {@link CamelContext} should be setup per test or per class.
102         * <p/>
103         * By default it will be setup/teardown per test (per test method). If you want to re-use
104         * {@link CamelContext} between test methods you can override this method and return <tt>true</tt>
105         * <p/>
106         * <b>Important:</b> Use this with care as the {@link CamelContext} will carry over state
107         * from previous tests, such as endpoints, components etc. So you cannot use this in all your tests.
108         * <p/>
109         * Setting up {@link CamelContext} uses the {@link #doPreSetup()}, {@link #doSetUp()}, and {@link #doPostSetup()}
110         * methods in that given order.
111         *
112         * @return <tt>true</tt> per class, <tt>false</tt> per test.
113         */
114        public boolean isCreateCamelContextPerClass() {
115            return false;
116        }
117    
118        /**
119         * Override to enable auto mocking endpoints based on the pattern.
120         * <p/>
121         * Return <tt>*</tt> to mock all endpoints.
122         *
123         * @see org.apache.camel.util.EndpointHelper#matchEndpoint(String, String)
124         */
125        public String isMockEndpoints() {
126            return null;
127        }
128    
129        public Service getCamelContextService() {
130            return camelContextService;
131        }
132    
133        public Service camelContextService() {
134            return camelContextService;
135        }
136    
137        public CamelContext context() {
138            return context;
139        }
140    
141        public ProducerTemplate template() {
142            return template;
143        }
144    
145        public ConsumerTemplate consumer() {
146            return consumer;
147        }
148    
149        /**
150         * Allows a service to be registered a separate lifecycle service to start
151         * and stop the context; such as for Spring when the ApplicationContext is
152         * started and stopped, rather than directly stopping the CamelContext
153         */
154        public void setCamelContextService(Service service) {
155            camelContextService = service;
156            threadService.set(camelContextService);
157        }
158    
159        @BeforeMethod
160        public void setUp() throws Exception {
161            log.info("********************************************************************************");
162            log.info("Testing: " + getTestMethodName() + "(" + getClass().getName() + ")");
163            log.info("********************************************************************************");
164    
165            if (isCreateCamelContextPerClass()) {
166                // test is per class, so only setup once (the first time)
167                boolean first = INIT.get() == null;
168                if (first) {
169                    doPreSetup();
170                    doSetUp();
171                    doPostSetup();
172                } else {
173                    // and in between tests we must do IoC and reset mocks
174                    postProcessTest();
175                    resetMocks();
176                }
177            } else {
178                // test is per test so always setup
179                doPreSetup();
180                doSetUp();
181                doPostSetup();
182            }
183    
184            // only start timing after all the setup
185            watch.restart();
186        }
187    
188        /**
189         * Strategy to perform any pre setup, before {@link CamelContext} is created
190         */
191        protected void doPreSetup() throws Exception {
192            // noop
193        }
194    
195        /**
196         * Strategy to perform any post setup after {@link CamelContext} is createt.
197         */
198        protected void doPostSetup() throws Exception {
199            // noop
200        }
201    
202        private void doSetUp() throws Exception {
203            log.debug("setUp test");
204            if (!useJmx()) {
205                disableJMX();
206            } else {
207                enableJMX();
208            }
209    
210            context = createCamelContext();
211            threadCamelContext.set(context);
212    
213            assertNotNull(context, "No context found!");
214    
215            // reduce default shutdown timeout to avoid waiting for 300 seconds
216            context.getShutdownStrategy().setTimeout(getShutdownTimeout());
217    
218            // set debugger
219            context.setDebugger(new DefaultDebugger());
220            context.getDebugger().addBreakpoint(breakpoint);
221            // note: when stopping CamelContext it will automatic remove the breakpoint
222    
223            template = context.createProducerTemplate();
224            template.start();
225            consumer = context.createConsumerTemplate();
226            consumer.start();
227    
228            threadTemplate.set(template);
229            threadConsumer.set(consumer);
230    
231            // enable auto mocking if enabled
232            String pattern = isMockEndpoints();
233            if (pattern != null) {
234                context.addRegisterEndpointCallback(new InterceptSendToMockEndpointStrategy(pattern));
235            }
236    
237            postProcessTest();
238    
239            if (isUseRouteBuilder()) {
240                RouteBuilder[] builders = createRouteBuilders();
241                for (RouteBuilder builder : builders) {
242                    log.debug("Using created route builder: " + builder);
243                    context.addRoutes(builder);
244                }
245                if (!"true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext"))) {
246                    startCamelContext();
247                } else {
248                    log.info("Skipping starting CamelContext as system property skipStartingCamelContext is set to be true.");
249                }
250            } else {
251                log.debug("Using route builder from the created context: " + context);
252            }
253            log.debug("Routing Rules are: " + context.getRoutes());
254    
255            assertValidContext(context);
256    
257            INIT.set(true);
258        }
259    
260        @AfterMethod
261        public void tearDown() throws Exception {
262            long time = watch.stop();
263    
264            log.info("********************************************************************************");
265            log.info("Testing done: " + getTestMethodName() + "(" + getClass().getName() + ")");
266            log.info("Took: " + TimeUtils.printDuration(time) + " ("  + time + " millis)");
267            log.info("********************************************************************************");
268    
269            if (isCreateCamelContextPerClass()) {
270                // we tear down in after class
271                return;
272            }
273    
274            LOG.debug("tearDown test");
275            doStopTemplates(consumer, template);
276            doStopCamelContext(context, camelContextService);
277        }
278    
279        @AfterClass
280        public static void tearDownAfterClass() throws Exception {
281            INIT.remove();
282            LOG.debug("tearDownAfterClass test");
283            doStopTemplates(threadConsumer.get(), threadTemplate.get());
284            doStopCamelContext(threadCamelContext.get(), threadService.get());
285        }
286    
287        /**
288         * Returns the timeout to use when shutting down (unit in seconds).
289         * <p/>
290         * Will default use 10 seconds.
291         *
292         * @return the timeout to use
293         */
294        protected int getShutdownTimeout() {
295            return 10;
296        }
297    
298        /**
299         * Whether or not JMX should be used during testing.
300         *
301         * @return <tt>false</tt> by default.
302         */
303        protected boolean useJmx() {
304            return false;
305        }
306    
307        /**
308         * Whether or not type converters should be lazy loaded (notice core converters is always loaded)
309         * <p/>
310         * We enabled lazy by default as it would speedup unit testing.
311         *
312         * @return <tt>true</tt> by default.
313         */
314        protected boolean isLazyLoadingTypeConverter() {
315            return true;
316        }
317    
318        /**
319         * Lets post process this test instance to process any Camel annotations.
320         * Note that using Spring Test or Guice is a more powerful approach.
321         */
322        protected void postProcessTest() throws Exception {
323            context = threadCamelContext.get();
324            template = threadTemplate.get();
325            consumer = threadConsumer.get();
326            camelContextService = threadService.get();
327    
328            CamelBeanPostProcessor processor = new CamelBeanPostProcessor();
329            processor.setCamelContext(context);
330            processor.postProcessBeforeInitialization(this, "this");
331        }
332    
333        protected void stopCamelContext() throws Exception {
334            doStopCamelContext(context, camelContextService);
335        }
336    
337        private static void doStopCamelContext(CamelContext context,
338                                               Service camelContextService) throws Exception {
339            if (camelContextService != null) {
340                if (camelContextService == threadService.get()) {
341                    threadService.remove();
342                }
343                camelContextService.stop();
344                camelContextService = null;
345            } else {
346                if (context != null) {
347                    if (context == threadCamelContext.get()) {
348                        threadCamelContext.remove();
349                    }
350                    context.stop();
351                    context = null;
352                }
353            }
354        }
355    
356        private static void doStopTemplates(ConsumerTemplate consumer,
357                                            ProducerTemplate template) throws Exception {
358            if (consumer != null) {
359                if (consumer == threadConsumer.get()) {
360                    threadConsumer.remove();
361                }
362                consumer.stop();
363                consumer = null;
364            }
365            if (template != null) {
366                if (template == threadTemplate.get()) {
367                    threadTemplate.remove();
368                }
369                template.stop();
370                template = null;
371            }
372        }
373    
374        protected void startCamelContext() throws Exception {
375            if (camelContextService != null) {
376                camelContextService.start();
377            } else {
378                if (context instanceof DefaultCamelContext) {
379                    DefaultCamelContext defaultCamelContext = (DefaultCamelContext)context;
380                    if (!defaultCamelContext.isStarted()) {
381                        defaultCamelContext.start();
382                    }
383                } else {
384                    context.start();
385                }
386            }
387        }
388    
389        protected CamelContext createCamelContext() throws Exception {
390            CamelContext context = new DefaultCamelContext(createRegistry());
391            context.setLazyLoadTypeConverters(isLazyLoadingTypeConverter());
392            return context;
393        }
394    
395        protected JndiRegistry createRegistry() throws Exception {
396            return new JndiRegistry(createJndiContext());
397        }
398    
399        @SuppressWarnings("unchecked")
400        protected Context createJndiContext() throws Exception {
401            Properties properties = new Properties();
402    
403            // jndi.properties is optional
404            InputStream in = getClass().getClassLoader().getResourceAsStream("jndi.properties");
405            if (in != null) {
406                log.debug("Using jndi.properties from classpath root");
407                properties.load(in);
408            } else {
409                properties.put("java.naming.factory.initial", "org.apache.camel.util.jndi.CamelInitialContextFactory");
410            }
411            return new InitialContext(new Hashtable(properties));
412        }
413    
414        /**
415         * Factory method which derived classes can use to create a {@link RouteBuilder}
416         * to define the routes for testing
417         */
418        protected RouteBuilder createRouteBuilder() throws Exception {
419            return new RouteBuilder() {
420                public void configure() {
421                    // no routes added by default
422                }
423            };
424        }
425    
426        /**
427         * Factory method which derived classes can use to create an array of
428         * {@link org.apache.camel.builder.RouteBuilder}s to define the routes for testing
429         *
430         * @see #createRouteBuilder()
431         */
432        protected RouteBuilder[] createRouteBuilders() throws Exception {
433            return new RouteBuilder[] {createRouteBuilder()};
434        }
435    
436        /**
437         * Resolves a mandatory endpoint for the given URI or an exception is thrown
438         *
439         * @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
440         * @return the endpoint
441         */
442        protected Endpoint resolveMandatoryEndpoint(String uri) {
443            return resolveMandatoryEndpoint(context, uri);
444        }
445    
446        /**
447         * Resolves a mandatory endpoint for the given URI and expected type or an exception is thrown
448         *
449         * @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
450         * @return the endpoint
451         */
452        protected <T extends Endpoint> T resolveMandatoryEndpoint(String uri, Class<T> endpointType) {
453            return resolveMandatoryEndpoint(context, uri, endpointType);
454        }
455    
456        /**
457         * Resolves the mandatory Mock endpoint using a URI of the form <code>mock:someName</code>
458         *
459         * @param uri the URI which typically starts with "mock:" and has some name
460         * @return the mandatory mock endpoint or an exception is thrown if it could not be resolved
461         */
462        protected MockEndpoint getMockEndpoint(String uri) {
463            return resolveMandatoryEndpoint(uri, MockEndpoint.class);
464        }
465    
466        /**
467         * Sends a message to the given endpoint URI with the body value
468         *
469         * @param endpointUri the URI of the endpoint to send to
470         * @param body        the body for the message
471         */
472        protected void sendBody(String endpointUri, final Object body) {
473            template.send(endpointUri, new Processor() {
474                public void process(Exchange exchange) {
475                    Message in = exchange.getIn();
476                    in.setBody(body);
477                }
478            });
479        }
480    
481        /**
482         * Sends a message to the given endpoint URI with the body value and specified headers
483         *
484         * @param endpointUri the URI of the endpoint to send to
485         * @param body        the body for the message
486         * @param headers     any headers to set on the message
487         */
488        protected void sendBody(String endpointUri, final Object body, final Map<String, Object> headers) {
489            template.send(endpointUri, new Processor() {
490                public void process(Exchange exchange) {
491                    Message in = exchange.getIn();
492                    in.setBody(body);
493                    for (Map.Entry<String, Object> entry : headers.entrySet()) {
494                        in.setHeader(entry.getKey(), entry.getValue());
495                    }
496                }
497            });
498        }
499    
500        /**
501         * Sends messages to the given endpoint for each of the specified bodies
502         *
503         * @param endpointUri the endpoint URI to send to
504         * @param bodies      the bodies to send, one per message
505         */
506        protected void sendBodies(String endpointUri, Object... bodies) {
507            for (Object body : bodies) {
508                sendBody(endpointUri, body);
509            }
510        }
511    
512        /**
513         * Creates an exchange with the given body
514         */
515        protected Exchange createExchangeWithBody(Object body) {
516            return createExchangeWithBody(context, body);
517        }
518    
519        /**
520         * Asserts that the given language name and expression evaluates to the
521         * given value on a specific exchange
522         */
523        protected void assertExpression(Exchange exchange, String languageName, String expressionText, Object expectedValue) {
524            Language language = assertResolveLanguage(languageName);
525    
526            Expression expression = language.createExpression(expressionText);
527            assertNotNull(expression, "No Expression could be created for text: " + expressionText + " language: " + language);
528    
529            assertExpression(expression, exchange, expectedValue);
530        }
531    
532        /**
533         * Asserts that the given language name and predicate expression evaluates
534         * to the expected value on the message exchange
535         */
536        protected void assertPredicate(String languageName, String expressionText, Exchange exchange, boolean expected) {
537            Language language = assertResolveLanguage(languageName);
538    
539            Predicate predicate = language.createPredicate(expressionText);
540            assertNotNull(predicate, "No Predicate could be created for text: " + expressionText + " language: " + language);
541    
542            assertPredicate(predicate, exchange, expected);
543        }
544    
545        /**
546         * Asserts that the language name can be resolved
547         */
548        protected Language assertResolveLanguage(String languageName) {
549            Language language = context.resolveLanguage(languageName);
550            assertNotNull(language, "No language found for name: " + languageName);
551            return language;
552        }
553    
554        /**
555         * Asserts that all the expectations of the Mock endpoints are valid
556         */
557        protected void assertMockEndpointsSatisfied() throws InterruptedException {
558            MockEndpoint.assertIsSatisfied(context);
559        }
560    
561        /**
562         * Asserts that all the expectations of the Mock endpoints are valid
563         */
564        protected void assertMockEndpointsSatisfied(long timeout, TimeUnit unit) throws InterruptedException {
565            MockEndpoint.assertIsSatisfied(context, timeout, unit);
566        }
567    
568        /**
569         * Reset all Mock endpoints.
570         */
571        protected void resetMocks() {
572            MockEndpoint.resetMocks(context);
573        }
574    
575        protected void assertValidContext(CamelContext context) {
576            assertNotNull(context, "No context found!");
577        }
578    
579        protected <T extends Endpoint> T getMandatoryEndpoint(String uri, Class<T> type) {
580            T endpoint = context.getEndpoint(uri, type);
581            assertNotNull(endpoint, "No endpoint found for uri: " + uri);
582            return endpoint;
583        }
584    
585        protected Endpoint getMandatoryEndpoint(String uri) {
586            Endpoint endpoint = context.getEndpoint(uri);
587            assertNotNull(endpoint, "No endpoint found for uri: " + uri);
588            return endpoint;
589        }
590    
591        /**
592         * Disables the JMX agent. Must be called before the {@link #setUp()} method.
593         */
594        protected void disableJMX() {
595            System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
596        }
597    
598        /**
599         * Enables the JMX agent. Must be called before the {@link #setUp()} method.
600         */
601        protected void enableJMX() {
602            System.setProperty(JmxSystemPropertyKeys.DISABLED, "false");
603        }
604    
605        /**
606         * Single step debugs and Camel invokes this method before entering the given processor
607         */
608        protected void debugBefore(Exchange exchange, Processor processor, ProcessorDefinition definition,
609                                   String id, String label) {
610        }
611    
612        /**
613         * Single step debugs and Camel invokes this method after processing the given processor
614         */
615        protected void debugAfter(Exchange exchange, Processor processor, ProcessorDefinition definition,
616                                  String id, String label, long timeTaken) {
617        }
618    
619        /**
620         * To easily debug by overriding the <tt>debugBefore</tt> and <tt>debugAfter</tt> methods.
621         */
622        private class DebugBreakpoint extends BreakpointSupport {
623    
624            @Override
625            public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition definition) {
626                CamelTestSupport.this.debugBefore(exchange, processor, definition, definition.getId(), definition.getLabel());
627            }
628    
629            @Override
630            public void afterProcess(Exchange exchange, Processor processor, ProcessorDefinition definition, long timeTaken) {
631                CamelTestSupport.this.debugAfter(exchange, processor, definition, definition.getId(), definition.getLabel(), timeTaken);
632            }
633        }
634    
635    }