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