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