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