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