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