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