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