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