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