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