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.testng;
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 java.util.concurrent.atomic.AtomicBoolean;
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.BreakpointSupport;
041 import org.apache.camel.impl.DefaultCamelContext;
042 import org.apache.camel.impl.DefaultDebugger;
043 import org.apache.camel.impl.InterceptSendToMockEndpointStrategy;
044 import org.apache.camel.impl.JndiRegistry;
045 import org.apache.camel.management.JmxSystemPropertyKeys;
046 import org.apache.camel.model.ProcessorDefinition;
047 import org.apache.camel.spi.Language;
048 import org.apache.camel.spring.CamelBeanPostProcessor;
049 import org.apache.camel.util.StopWatch;
050 import org.apache.camel.util.TimeUtils;
051 import org.slf4j.Logger;
052 import org.slf4j.LoggerFactory;
053 import org.testng.annotations.AfterClass;
054 import org.testng.annotations.AfterMethod;
055 import org.testng.annotations.BeforeMethod;
056
057 /**
058 * A useful base class which creates a {@link org.apache.camel.CamelContext} with some routes
059 * along with a {@link org.apache.camel.ProducerTemplate} for use in the test case
060 *
061 * @version $Revision: 1172870 $
062 */
063 public abstract class CamelTestSupport extends TestSupport {
064 private static final Logger LOG = LoggerFactory.getLogger(TestSupport.class);
065 private static final ThreadLocal<Boolean> INIT = new ThreadLocal<Boolean>();
066
067
068 private static ThreadLocal<CamelContext> threadCamelContext
069 = new ThreadLocal<CamelContext>();
070 private static ThreadLocal<ProducerTemplate> threadTemplate
071 = new ThreadLocal<ProducerTemplate>();
072 private static ThreadLocal<ConsumerTemplate> threadConsumer
073 = new ThreadLocal<ConsumerTemplate>();
074 private static ThreadLocal<Service> threadService
075 = new ThreadLocal<Service>();
076
077 protected volatile CamelContext context;
078 protected volatile ProducerTemplate template;
079 protected volatile ConsumerTemplate consumer;
080 protected volatile Service camelContextService;
081
082
083 private boolean useRouteBuilder = true;
084 private final DebugBreakpoint breakpoint = new DebugBreakpoint();
085 private final StopWatch watch = new StopWatch();
086
087 /**
088 * Use the RouteBuilder or not
089 * @return <tt>true</tt> then {@link CamelContext} will be auto started,
090 * <tt>false</tt> then {@link CamelContext} will <b>not</b> be auto started (you will have to start it manually)
091 */
092 public boolean isUseRouteBuilder() {
093 return useRouteBuilder;
094 }
095
096 public void setUseRouteBuilder(boolean useRouteBuilder) {
097 this.useRouteBuilder = useRouteBuilder;
098 }
099
100 /**
101 * Override to control whether {@link CamelContext} should be setup per test or per class.
102 * <p/>
103 * By default it will be setup/teardown per test (per test method). If you want to re-use
104 * {@link CamelContext} between test methods you can override this method and return <tt>true</tt>
105 * <p/>
106 * <b>Important:</b> Use this with care as the {@link CamelContext} will carry over state
107 * from previous tests, such as endpoints, components etc. So you cannot use this in all your tests.
108 * <p/>
109 * Setting up {@link CamelContext} uses the {@link #doPreSetup()}, {@link #doSetUp()}, and {@link #doPostSetup()}
110 * methods in that given order.
111 *
112 * @return <tt>true</tt> per class, <tt>false</tt> per test.
113 */
114 public boolean isCreateCamelContextPerClass() {
115 return false;
116 }
117
118 /**
119 * Override to enable auto mocking endpoints based on the pattern.
120 * <p/>
121 * Return <tt>*</tt> to mock all endpoints.
122 *
123 * @see org.apache.camel.util.EndpointHelper#matchEndpoint(String, String)
124 */
125 public String isMockEndpoints() {
126 return null;
127 }
128
129 public Service getCamelContextService() {
130 return camelContextService;
131 }
132
133 public Service camelContextService() {
134 return camelContextService;
135 }
136
137 public CamelContext context() {
138 return context;
139 }
140
141 public ProducerTemplate template() {
142 return template;
143 }
144
145 public ConsumerTemplate consumer() {
146 return consumer;
147 }
148
149 /**
150 * Allows a service to be registered a separate lifecycle service to start
151 * and stop the context; such as for Spring when the ApplicationContext is
152 * started and stopped, rather than directly stopping the CamelContext
153 */
154 public void setCamelContextService(Service service) {
155 camelContextService = service;
156 threadService.set(camelContextService);
157 }
158
159 @BeforeMethod
160 public void setUp() throws Exception {
161 log.info("********************************************************************************");
162 log.info("Testing: " + getTestMethodName() + "(" + getClass().getName() + ")");
163 log.info("********************************************************************************");
164
165 if (isCreateCamelContextPerClass()) {
166 // test is per class, so only setup once (the first time)
167 boolean first = INIT.get() == null;
168 if (first) {
169 doPreSetup();
170 doSetUp();
171 doPostSetup();
172 } else {
173 // and in between tests we must do IoC and reset mocks
174 postProcessTest();
175 resetMocks();
176 }
177 } else {
178 // test is per test so always setup
179 doPreSetup();
180 doSetUp();
181 doPostSetup();
182 }
183
184 // only start timing after all the setup
185 watch.restart();
186 }
187
188 /**
189 * Strategy to perform any pre setup, before {@link CamelContext} is created
190 */
191 protected void doPreSetup() throws Exception {
192 // noop
193 }
194
195 /**
196 * Strategy to perform any post setup after {@link CamelContext} is createt.
197 */
198 protected void doPostSetup() throws Exception {
199 // noop
200 }
201
202 private void doSetUp() throws Exception {
203 log.debug("setUp test");
204 if (!useJmx()) {
205 disableJMX();
206 } else {
207 enableJMX();
208 }
209
210 context = createCamelContext();
211 threadCamelContext.set(context);
212
213 assertNotNull(context, "No context found!");
214
215 // reduce default shutdown timeout to avoid waiting for 300 seconds
216 context.getShutdownStrategy().setTimeout(getShutdownTimeout());
217
218 // set debugger
219 context.setDebugger(new DefaultDebugger());
220 context.getDebugger().addBreakpoint(breakpoint);
221 // note: when stopping CamelContext it will automatic remove the breakpoint
222
223 template = context.createProducerTemplate();
224 template.start();
225 consumer = context.createConsumerTemplate();
226 consumer.start();
227
228 threadTemplate.set(template);
229 threadConsumer.set(consumer);
230
231 // enable auto mocking if enabled
232 String pattern = isMockEndpoints();
233 if (pattern != null) {
234 context.addRegisterEndpointCallback(new InterceptSendToMockEndpointStrategy(pattern));
235 }
236
237 postProcessTest();
238
239 if (isUseRouteBuilder()) {
240 RouteBuilder[] builders = createRouteBuilders();
241 for (RouteBuilder builder : builders) {
242 log.debug("Using created route builder: " + builder);
243 context.addRoutes(builder);
244 }
245 if (!"true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext"))) {
246 startCamelContext();
247 } else {
248 log.info("Skipping starting CamelContext as system property skipStartingCamelContext is set to be true.");
249 }
250 } else {
251 log.debug("Using route builder from the created context: " + context);
252 }
253 log.debug("Routing Rules are: " + context.getRoutes());
254
255 assertValidContext(context);
256
257 INIT.set(true);
258 }
259
260 @AfterMethod
261 public void tearDown() throws Exception {
262 long time = watch.stop();
263
264 log.info("********************************************************************************");
265 log.info("Testing done: " + getTestMethodName() + "(" + getClass().getName() + ")");
266 log.info("Took: " + TimeUtils.printDuration(time) + " (" + time + " millis)");
267 log.info("********************************************************************************");
268
269 if (isCreateCamelContextPerClass()) {
270 // we tear down in after class
271 return;
272 }
273
274 LOG.debug("tearDown test");
275 doStopTemplates(consumer, template);
276 doStopCamelContext(context, camelContextService);
277 }
278
279 @AfterClass
280 public static void tearDownAfterClass() throws Exception {
281 INIT.remove();
282 LOG.debug("tearDownAfterClass test");
283 doStopTemplates(threadConsumer.get(), threadTemplate.get());
284 doStopCamelContext(threadCamelContext.get(), threadService.get());
285 }
286
287 /**
288 * Returns the timeout to use when shutting down (unit in seconds).
289 * <p/>
290 * Will default use 10 seconds.
291 *
292 * @return the timeout to use
293 */
294 protected int getShutdownTimeout() {
295 return 10;
296 }
297
298 /**
299 * Whether or not JMX should be used during testing.
300 *
301 * @return <tt>false</tt> by default.
302 */
303 protected boolean useJmx() {
304 return false;
305 }
306
307 /**
308 * Whether or not type converters should be lazy loaded (notice core converters is always loaded)
309 * <p/>
310 * We enabled lazy by default as it would speedup unit testing.
311 *
312 * @return <tt>true</tt> by default.
313 */
314 protected boolean isLazyLoadingTypeConverter() {
315 return true;
316 }
317
318 /**
319 * Lets post process this test instance to process any Camel annotations.
320 * Note that using Spring Test or Guice is a more powerful approach.
321 */
322 protected void postProcessTest() throws Exception {
323 context = threadCamelContext.get();
324 template = threadTemplate.get();
325 consumer = threadConsumer.get();
326 camelContextService = threadService.get();
327
328 CamelBeanPostProcessor processor = new CamelBeanPostProcessor();
329 processor.setCamelContext(context);
330 processor.postProcessBeforeInitialization(this, "this");
331 }
332
333 protected void stopCamelContext() throws Exception {
334 doStopCamelContext(context, camelContextService);
335 }
336
337 private static void doStopCamelContext(CamelContext context,
338 Service camelContextService) throws Exception {
339 if (camelContextService != null) {
340 if (camelContextService == threadService.get()) {
341 threadService.remove();
342 }
343 camelContextService.stop();
344 camelContextService = null;
345 } else {
346 if (context != null) {
347 if (context == threadCamelContext.get()) {
348 threadCamelContext.remove();
349 }
350 context.stop();
351 context = null;
352 }
353 }
354 }
355
356 private static void doStopTemplates(ConsumerTemplate consumer,
357 ProducerTemplate template) throws Exception {
358 if (consumer != null) {
359 if (consumer == threadConsumer.get()) {
360 threadConsumer.remove();
361 }
362 consumer.stop();
363 consumer = null;
364 }
365 if (template != null) {
366 if (template == threadTemplate.get()) {
367 threadTemplate.remove();
368 }
369 template.stop();
370 template = null;
371 }
372 }
373
374 protected void startCamelContext() throws Exception {
375 if (camelContextService != null) {
376 camelContextService.start();
377 } else {
378 if (context instanceof DefaultCamelContext) {
379 DefaultCamelContext defaultCamelContext = (DefaultCamelContext)context;
380 if (!defaultCamelContext.isStarted()) {
381 defaultCamelContext.start();
382 }
383 } else {
384 context.start();
385 }
386 }
387 }
388
389 protected CamelContext createCamelContext() throws Exception {
390 CamelContext context = new DefaultCamelContext(createRegistry());
391 context.setLazyLoadTypeConverters(isLazyLoadingTypeConverter());
392 return context;
393 }
394
395 protected JndiRegistry createRegistry() throws Exception {
396 return new JndiRegistry(createJndiContext());
397 }
398
399 @SuppressWarnings("unchecked")
400 protected Context createJndiContext() throws Exception {
401 Properties properties = new Properties();
402
403 // jndi.properties is optional
404 InputStream in = getClass().getClassLoader().getResourceAsStream("jndi.properties");
405 if (in != null) {
406 log.debug("Using jndi.properties from classpath root");
407 properties.load(in);
408 } else {
409 properties.put("java.naming.factory.initial", "org.apache.camel.util.jndi.CamelInitialContextFactory");
410 }
411 return new InitialContext(new Hashtable(properties));
412 }
413
414 /**
415 * Factory method which derived classes can use to create a {@link RouteBuilder}
416 * to define the routes for testing
417 */
418 protected RouteBuilder createRouteBuilder() throws Exception {
419 return new RouteBuilder() {
420 public void configure() {
421 // no routes added by default
422 }
423 };
424 }
425
426 /**
427 * Factory method which derived classes can use to create an array of
428 * {@link org.apache.camel.builder.RouteBuilder}s to define the routes for testing
429 *
430 * @see #createRouteBuilder()
431 */
432 protected RouteBuilder[] createRouteBuilders() throws Exception {
433 return new RouteBuilder[] {createRouteBuilder()};
434 }
435
436 /**
437 * Resolves a mandatory endpoint for the given URI or an exception is thrown
438 *
439 * @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
440 * @return the endpoint
441 */
442 protected Endpoint resolveMandatoryEndpoint(String uri) {
443 return resolveMandatoryEndpoint(context, uri);
444 }
445
446 /**
447 * Resolves a mandatory endpoint for the given URI and expected type or an exception is thrown
448 *
449 * @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
450 * @return the endpoint
451 */
452 protected <T extends Endpoint> T resolveMandatoryEndpoint(String uri, Class<T> endpointType) {
453 return resolveMandatoryEndpoint(context, uri, endpointType);
454 }
455
456 /**
457 * Resolves the mandatory Mock endpoint using a URI of the form <code>mock:someName</code>
458 *
459 * @param uri the URI which typically starts with "mock:" and has some name
460 * @return the mandatory mock endpoint or an exception is thrown if it could not be resolved
461 */
462 protected MockEndpoint getMockEndpoint(String uri) {
463 return resolveMandatoryEndpoint(uri, MockEndpoint.class);
464 }
465
466 /**
467 * Sends a message to the given endpoint URI with the body value
468 *
469 * @param endpointUri the URI of the endpoint to send to
470 * @param body the body for the message
471 */
472 protected void sendBody(String endpointUri, final Object body) {
473 template.send(endpointUri, new Processor() {
474 public void process(Exchange exchange) {
475 Message in = exchange.getIn();
476 in.setBody(body);
477 }
478 });
479 }
480
481 /**
482 * Sends a message to the given endpoint URI with the body value and specified headers
483 *
484 * @param endpointUri the URI of the endpoint to send to
485 * @param body the body for the message
486 * @param headers any headers to set on the message
487 */
488 protected void sendBody(String endpointUri, final Object body, final Map<String, Object> headers) {
489 template.send(endpointUri, new Processor() {
490 public void process(Exchange exchange) {
491 Message in = exchange.getIn();
492 in.setBody(body);
493 for (Map.Entry<String, Object> entry : headers.entrySet()) {
494 in.setHeader(entry.getKey(), entry.getValue());
495 }
496 }
497 });
498 }
499
500 /**
501 * Sends messages to the given endpoint for each of the specified bodies
502 *
503 * @param endpointUri the endpoint URI to send to
504 * @param bodies the bodies to send, one per message
505 */
506 protected void sendBodies(String endpointUri, Object... bodies) {
507 for (Object body : bodies) {
508 sendBody(endpointUri, body);
509 }
510 }
511
512 /**
513 * Creates an exchange with the given body
514 */
515 protected Exchange createExchangeWithBody(Object body) {
516 return createExchangeWithBody(context, body);
517 }
518
519 /**
520 * Asserts that the given language name and expression evaluates to the
521 * given value on a specific exchange
522 */
523 protected void assertExpression(Exchange exchange, String languageName, String expressionText, Object expectedValue) {
524 Language language = assertResolveLanguage(languageName);
525
526 Expression expression = language.createExpression(expressionText);
527 assertNotNull(expression, "No Expression could be created for text: " + expressionText + " language: " + language);
528
529 assertExpression(expression, exchange, expectedValue);
530 }
531
532 /**
533 * Asserts that the given language name and predicate expression evaluates
534 * to the expected value on the message exchange
535 */
536 protected void assertPredicate(String languageName, String expressionText, Exchange exchange, boolean expected) {
537 Language language = assertResolveLanguage(languageName);
538
539 Predicate predicate = language.createPredicate(expressionText);
540 assertNotNull(predicate, "No Predicate could be created for text: " + expressionText + " language: " + language);
541
542 assertPredicate(predicate, exchange, expected);
543 }
544
545 /**
546 * Asserts that the language name can be resolved
547 */
548 protected Language assertResolveLanguage(String languageName) {
549 Language language = context.resolveLanguage(languageName);
550 assertNotNull(language, "No language found for name: " + languageName);
551 return language;
552 }
553
554 /**
555 * Asserts that all the expectations of the Mock endpoints are valid
556 */
557 protected void assertMockEndpointsSatisfied() throws InterruptedException {
558 MockEndpoint.assertIsSatisfied(context);
559 }
560
561 /**
562 * Asserts that all the expectations of the Mock endpoints are valid
563 */
564 protected void assertMockEndpointsSatisfied(long timeout, TimeUnit unit) throws InterruptedException {
565 MockEndpoint.assertIsSatisfied(context, timeout, unit);
566 }
567
568 /**
569 * Reset all Mock endpoints.
570 */
571 protected void resetMocks() {
572 MockEndpoint.resetMocks(context);
573 }
574
575 protected void assertValidContext(CamelContext context) {
576 assertNotNull(context, "No context found!");
577 }
578
579 protected <T extends Endpoint> T getMandatoryEndpoint(String uri, Class<T> type) {
580 T endpoint = context.getEndpoint(uri, type);
581 assertNotNull(endpoint, "No endpoint found for uri: " + uri);
582 return endpoint;
583 }
584
585 protected Endpoint getMandatoryEndpoint(String uri) {
586 Endpoint endpoint = context.getEndpoint(uri);
587 assertNotNull(endpoint, "No endpoint found for uri: " + uri);
588 return endpoint;
589 }
590
591 /**
592 * Disables the JMX agent. Must be called before the {@link #setUp()} method.
593 */
594 protected void disableJMX() {
595 System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
596 }
597
598 /**
599 * Enables the JMX agent. Must be called before the {@link #setUp()} method.
600 */
601 protected void enableJMX() {
602 System.setProperty(JmxSystemPropertyKeys.DISABLED, "false");
603 }
604
605 /**
606 * Single step debugs and Camel invokes this method before entering the given processor
607 */
608 protected void debugBefore(Exchange exchange, Processor processor, ProcessorDefinition definition,
609 String id, String label) {
610 }
611
612 /**
613 * Single step debugs and Camel invokes this method after processing the given processor
614 */
615 protected void debugAfter(Exchange exchange, Processor processor, ProcessorDefinition definition,
616 String id, String label, long timeTaken) {
617 }
618
619 /**
620 * To easily debug by overriding the <tt>debugBefore</tt> and <tt>debugAfter</tt> methods.
621 */
622 private class DebugBreakpoint extends BreakpointSupport {
623
624 @Override
625 public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition definition) {
626 CamelTestSupport.this.debugBefore(exchange, processor, definition, definition.getId(), definition.getLabel());
627 }
628
629 @Override
630 public void afterProcess(Exchange exchange, Processor processor, ProcessorDefinition definition, long timeTaken) {
631 CamelTestSupport.this.debugAfter(exchange, processor, definition, definition.getId(), definition.getLabel(), timeTaken);
632 }
633 }
634
635 }