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