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