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