/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.testng;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.apache.camel.CamelContext;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Message;
import org.apache.camel.NoSuchEndpointException;
import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.Service;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.impl.BreakpointSupport;
import org.apache.camel.impl.DefaultCamelBeanPostProcessor;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.DefaultDebugger;
import org.apache.camel.impl.InterceptSendToMockEndpointStrategy;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.management.JmxSystemPropertyKeys;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.spi.Language;
import org.apache.camel.util.StopWatch;
import org.apache.camel.util.TimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
/**
* A useful base class which creates a {@link org.apache.camel.CamelContext} with some routes
* along with a {@link org.apache.camel.ProducerTemplate} for use in the test case
*/
public abstract class CamelTestSupport extends TestSupport {
private static final Logger LOG = LoggerFactory.getLogger(TestSupport.class);
private static final ThreadLocal<Boolean> INIT = new ThreadLocal<Boolean>();
private static ThreadLocal<ModelCamelContext> threadCamelContext
= new ThreadLocal<ModelCamelContext>();
private static ThreadLocal<ProducerTemplate> threadTemplate
= new ThreadLocal<ProducerTemplate>();
private static ThreadLocal<ConsumerTemplate> threadConsumer
= new ThreadLocal<ConsumerTemplate>();
private static ThreadLocal<Service> threadService
= new ThreadLocal<Service>();
protected volatile ModelCamelContext context;
protected volatile ProducerTemplate template;
protected volatile ConsumerTemplate consumer;
protected volatile Service camelContextService;
private boolean useRouteBuilder = true;
private final DebugBreakpoint breakpoint = new DebugBreakpoint();
private final StopWatch watch = new StopWatch();
/**
* Use the RouteBuilder or not
* @return <tt>true</tt> then {@link CamelContext} will be auto started,
* <tt>false</tt> then {@link CamelContext} will <b>not</b> be auto started (you will have to start it manually)
*/
public boolean isUseRouteBuilder() {
return useRouteBuilder;
}
public void setUseRouteBuilder(boolean useRouteBuilder) {
this.useRouteBuilder = useRouteBuilder;
}
/**
* Override when using <a href="http://camel.apache.org/advicewith.html">advice with</a> and return <tt>true</tt>.
* This helps knowing advice with is to be used, and {@link CamelContext} will not be started before
* the advice with takes place. This helps by ensuring the advice with has been property setup before the
* {@link CamelContext} is started
* <p/>
* <b>Important:</b> Its important to start {@link CamelContext} manually from the unit test
* after you are done doing all the advice with.
*
* @return <tt>true</tt> if you use advice with in your unit tests.
*/
public boolean isUseAdviceWith() {
return false;
}
/**
* Override to control whether {@link CamelContext} should be setup per test or per class.
* <p/>
* By default it will be setup/teardown per test (per test method). If you want to re-use
* {@link CamelContext} between test methods you can override this method and return <tt>true</tt>
* <p/>
* <b>Important:</b> Use this with care as the {@link CamelContext} will carry over state
* from previous tests, such as endpoints, components etc. So you cannot use this in all your tests.
* <p/>
* Setting up {@link CamelContext} uses the {@link #doPreSetup()}, {@link #doSetUp()}, and {@link #doPostSetup()}
* methods in that given order.
*
* @return <tt>true</tt> per class, <tt>false</tt> per test.
*/
public boolean isCreateCamelContextPerClass() {
return false;
}
/**
* Override to enable auto mocking endpoints based on the pattern.
* <p/>
* Return <tt>*</tt> to mock all endpoints.
*
* @see org.apache.camel.util.EndpointHelper#matchEndpoint(String, String)
*/
public String isMockEndpoints() {
return null;
}
/**
* Override to enable auto mocking endpoints based on the pattern, and <b>skip</b> sending
* to original endpoint.
* <p/>
* Return <tt>*</tt> to mock all endpoints.
*
* @see org.apache.camel.util.EndpointHelper#matchEndpoint(String, String)
*/
public String isMockEndpointsAndSkip() {
return null;
}
/**
* Override to enable debugger
* <p/>
* Is default <tt>false</tt>
*/
public boolean isUseDebugger() {
return false;
}
public Service getCamelContextService() {
return camelContextService;
}
public Service camelContextService() {
return camelContextService;
}
public CamelContext context() {
return context;
}
public ProducerTemplate template() {
return template;
}
public ConsumerTemplate consumer() {
return consumer;
}
/**
* Allows a service to be registered a separate lifecycle service to start
* and stop the context; such as for Spring when the ApplicationContext is
* started and stopped, rather than directly stopping the CamelContext
*/
public void setCamelContextService(Service service) {
camelContextService = service;
threadService.set(camelContextService);
}
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception {
log.info("********************************************************************************");
log.info("Testing: " + getTestMethodName() + "(" + getClass().getName() + ")");
log.info("********************************************************************************");
if (isCreateCamelContextPerClass()) {
// test is per class, so only setup once (the first time)
boolean first = INIT.get() == null;
if (first) {
doPreSetup();
doSetUp();
doPostSetup();
} else {
// and in between tests we must do IoC and reset mocks
postProcessTest();
resetMocks();
}
} else {
// test is per test so always setup
doPreSetup();
doSetUp();
doPostSetup();
}
// only start timing after all the setup
watch.restart();
}
/**
* Strategy to perform any pre setup, before {@link CamelContext} is created
*/
protected void doPreSetup() throws Exception {
// noop
}
/**
* Strategy to perform any post setup after {@link CamelContext} is createt.
*/
protected void doPostSetup() throws Exception {
// noop
}
private void doSetUp() throws Exception {
log.debug("setUp test");
if (!useJmx()) {
disableJMX();
} else {
enableJMX();
}
CamelContext c2 = createCamelContext();
if (c2 instanceof ModelCamelContext) {
context = (ModelCamelContext)c2;
} else {
throw new Exception("Context must be a ModelCamelContext");
}
threadCamelContext.set(context);
assertNotNull(context, "No context found!");
// reduce default shutdown timeout to avoid waiting for 300 seconds
context.getShutdownStrategy().setTimeout(getShutdownTimeout());
// set debugger if enabled
if (isUseDebugger()) {
context.setDebugger(new DefaultDebugger());
context.getDebugger().addBreakpoint(breakpoint);
// note: when stopping CamelContext it will automatic remove the breakpoint
}
template = context.createProducerTemplate();
template.start();
consumer = context.createConsumerTemplate();
consumer.start();
threadTemplate.set(template);
threadConsumer.set(consumer);
// enable auto mocking if enabled
String pattern = isMockEndpoints();
if (pattern != null) {
context.addRegisterEndpointCallback(new InterceptSendToMockEndpointStrategy(pattern));
}
pattern = isMockEndpointsAndSkip();
if (pattern != null) {
context.addRegisterEndpointCallback(new InterceptSendToMockEndpointStrategy(pattern, true));
}
// configure properties component (mandatory for testing)
context.getComponent("properties", PropertiesComponent.class);
postProcessTest();
if (isUseRouteBuilder()) {
RoutesBuilder[] builders = createRouteBuilders();
for (RoutesBuilder builder : builders) {
log.debug("Using created route builder: " + builder);
context.addRoutes(builder);
}
boolean skip = "true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext"));
if (skip) {
log.info("Skipping starting CamelContext as system property skipStartingCamelContext is set to be true.");
} else if (isUseAdviceWith()) {
log.info("Skipping starting CamelContext as isUseAdviceWith is set to true.");
} else {
startCamelContext();
}
} else {
log.debug("Using route builder from the created context: " + context);
}
log.debug("Routing Rules are: " + context.getRoutes());
assertValidContext(context);
INIT.set(true);
}
@AfterMethod(alwaysRun = true)
public void tearDown() throws Exception {
long time = watch.stop();
log.info("********************************************************************************");
log.info("Testing done: " + getTestMethodName() + "(" + getClass().getName() + ")");
log.info("Took: " + TimeUtils.printDuration(time) + " (" + time + " millis)");
log.info("********************************************************************************");
if (isCreateCamelContextPerClass()) {
// we tear down in after class
return;
}
LOG.debug("tearDown test");
doStopTemplates(consumer, template);
doStopCamelContext(context, camelContextService);
}
@AfterClass(alwaysRun = true)
public static void tearDownAfterClass() throws Exception {
INIT.remove();
LOG.debug("tearDownAfterClass test");
doStopTemplates(threadConsumer.get(), threadTemplate.get());
doStopCamelContext(threadCamelContext.get(), threadService.get());
}
/**
* Returns the timeout to use when shutting down (unit in seconds).
* <p/>
* Will default use 10 seconds.
*
* @return the timeout to use
*/
protected int getShutdownTimeout() {
return 10;
}
/**
* Whether or not JMX should be used during testing.
*
* @return <tt>false</tt> by default.
*/
protected boolean useJmx() {
return false;
}
/**
* Lets post process this test instance to process any Camel annotations.
* Note that using Spring Test or Guice is a more powerful approach.
*/
protected void postProcessTest() throws Exception {
context = threadCamelContext.get();
template = threadTemplate.get();
consumer = threadConsumer.get();
camelContextService = threadService.get();
// use the default bean post processor from camel-core
DefaultCamelBeanPostProcessor processor = new DefaultCamelBeanPostProcessor(context);
processor.postProcessBeforeInitialization(this, getClass().getName());
processor.postProcessAfterInitialization(this, getClass().getName());
}
protected void stopCamelContext() throws Exception {
doStopCamelContext(context, camelContextService);
}
private static void doStopCamelContext(CamelContext context,
Service camelContextService) throws Exception {
if (camelContextService != null) {
if (camelContextService == threadService.get()) {
threadService.remove();
}
camelContextService.stop();
camelContextService = null;
} else {
if (context != null) {
if (context == threadCamelContext.get()) {
threadCamelContext.remove();
}
context.stop();
context = null;
}
}
}
private static void doStopTemplates(ConsumerTemplate consumer,
ProducerTemplate template) throws Exception {
if (consumer != null) {
if (consumer == threadConsumer.get()) {
threadConsumer.remove();
}
consumer.stop();
consumer = null;
}
if (template != null) {
if (template == threadTemplate.get()) {
threadTemplate.remove();
}
template.stop();
template = null;
}
}
protected void startCamelContext() throws Exception {
if (camelContextService != null) {
camelContextService.start();
} else {
if (context instanceof DefaultCamelContext) {
DefaultCamelContext defaultCamelContext = (DefaultCamelContext)context;
if (!defaultCamelContext.isStarted()) {
defaultCamelContext.start();
}
} else {
context.start();
}
}
}
protected CamelContext createCamelContext() throws Exception {
CamelContext context = new DefaultCamelContext(createRegistry());
return context;
}
protected JndiRegistry createRegistry() throws Exception {
return new JndiRegistry(createJndiContext());
}
protected Context createJndiContext() throws Exception {
Properties properties = new Properties();
// jndi.properties is optional
InputStream in = getClass().getClassLoader().getResourceAsStream("jndi.properties");
if (in != null) {
log.debug("Using jndi.properties from classpath root");
properties.load(in);
} else {
properties.put("java.naming.factory.initial", "org.apache.camel.util.jndi.CamelInitialContextFactory");
}
return new InitialContext(new Hashtable<Object, Object>(properties));
}
/**
* Factory method which derived classes can use to create a {@link RoutesBuilder}
* to define the routes for testing
*/
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
public void configure() {
// no routes added by default
}
};
}
/**
* Factory method which derived classes can use to create an array of
* {@link org.apache.camel.RoutesBuilder}s to define the routes for testing
*
* @see #createRouteBuilder()
*/
protected RoutesBuilder[] createRouteBuilders() throws Exception {
return new RoutesBuilder[] {createRouteBuilder()};
}
/**
* Resolves a mandatory endpoint for the given URI or an exception is thrown
*
* @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
* @return the endpoint
*/
protected Endpoint resolveMandatoryEndpoint(String uri) {
return resolveMandatoryEndpoint(context, uri);
}
/**
* Resolves a mandatory endpoint for the given URI and expected type or an exception is thrown
*
* @param uri the Camel <a href="">URI</a> to use to create or resolve an endpoint
* @return the endpoint
*/
protected <T extends Endpoint> T resolveMandatoryEndpoint(String uri, Class<T> endpointType) {
return resolveMandatoryEndpoint(context, uri, endpointType);
}
/**
* Resolves the mandatory Mock endpoint using a URI of the form <code>mock:someName</code>
*
* @param uri the URI which typically starts with "mock:" and has some name
* @return the mandatory mock endpoint or an exception is thrown if it could not be resolved
*/
protected MockEndpoint getMockEndpoint(String uri) {
return getMockEndpoint(uri, true);
}
/**
* Resolves the {@link MockEndpoint} using a URI of the form <code>mock:someName</code>, optionally
* creating it if it does not exist.
*
* @param uri the URI which typically starts with "mock:" and has some name
* @param create whether or not to allow the endpoint to be created if it doesn't exist
* @return the mock endpoint or an {@link NoSuchEndpointException} is thrown if it could not be resolved
* @throws NoSuchEndpointException is the mock endpoint does not exists
*/
protected MockEndpoint getMockEndpoint(String uri, boolean create) throws NoSuchEndpointException {
if (create) {
return resolveMandatoryEndpoint(uri, MockEndpoint.class);
} else {
Endpoint endpoint = context.hasEndpoint(uri);
if (endpoint instanceof MockEndpoint) {
return (MockEndpoint) endpoint;
}
throw new NoSuchEndpointException(String.format("MockEndpoint %s does not exist.", uri));
}
}
/**
* Sends a message to the given endpoint URI with the body value
*
* @param endpointUri the URI of the endpoint to send to
* @param body the body for the message
*/
protected void sendBody(String endpointUri, final Object body) {
template.send(endpointUri, new Processor() {
public void process(Exchange exchange) {
Message in = exchange.getIn();
in.setBody(body);
}
});
}
/**
* Sends a message to the given endpoint URI with the body value and specified headers
*
* @param endpointUri the URI of the endpoint to send to
* @param body the body for the message
* @param headers any headers to set on the message
*/
protected void sendBody(String endpointUri, final Object body, final Map<String, Object> headers) {
template.send(endpointUri, new Processor() {
public void process(Exchange exchange) {
Message in = exchange.getIn();
in.setBody(body);
for (Map.Entry<String, Object> entry : headers.entrySet()) {
in.setHeader(entry.getKey(), entry.getValue());
}
}
});
}
/**
* Sends messages to the given endpoint for each of the specified bodies
*
* @param endpointUri the endpoint URI to send to
* @param bodies the bodies to send, one per message
*/
protected void sendBodies(String endpointUri, Object... bodies) {
for (Object body : bodies) {
sendBody(endpointUri, body);
}
}
/**
* Creates an exchange with the given body
*/
protected Exchange createExchangeWithBody(Object body) {
return createExchangeWithBody(context, body);
}
/**
* Asserts that the given language name and expression evaluates to the
* given value on a specific exchange
*/
protected void assertExpression(Exchange exchange, String languageName, String expressionText, Object expectedValue) {
Language language = assertResolveLanguage(languageName);
Expression expression = language.createExpression(expressionText);
assertNotNull(expression, "No Expression could be created for text: " + expressionText + " language: " + language);
assertExpression(expression, exchange, expectedValue);
}
/**
* Asserts that the given language name and predicate expression evaluates
* to the expected value on the message exchange
*/
protected void assertPredicate(String languageName, String expressionText, Exchange exchange, boolean expected) {
Language language = assertResolveLanguage(languageName);
Predicate predicate = language.createPredicate(expressionText);
assertNotNull(predicate, "No Predicate could be created for text: " + expressionText + " language: " + language);
assertPredicate(predicate, exchange, expected);
}
/**
* Asserts that the language name can be resolved
*/
protected Language assertResolveLanguage(String languageName) {
Language language = context.resolveLanguage(languageName);
assertNotNull(language, "No language found for name: " + languageName);
return language;
}
/**
* Asserts that all the expectations of the Mock endpoints are valid
*/
protected void assertMockEndpointsSatisfied() throws InterruptedException {
MockEndpoint.assertIsSatisfied(context);
}
/**
* Asserts that all the expectations of the Mock endpoints are valid
*/
protected void assertMockEndpointsSatisfied(long timeout, TimeUnit unit) throws InterruptedException {
MockEndpoint.assertIsSatisfied(context, timeout, unit);
}
/**
* Reset all Mock endpoints.
*/
protected void resetMocks() {
MockEndpoint.resetMocks(context);
}
protected void assertValidContext(CamelContext context) {
assertNotNull(context, "No context found!");
}
protected <T extends Endpoint> T getMandatoryEndpoint(String uri, Class<T> type) {
T endpoint = context.getEndpoint(uri, type);
assertNotNull(endpoint, "No endpoint found for uri: " + uri);
return endpoint;
}
protected Endpoint getMandatoryEndpoint(String uri) {
Endpoint endpoint = context.getEndpoint(uri);
assertNotNull(endpoint, "No endpoint found for uri: " + uri);
return endpoint;
}
/**
* Disables the JMX agent. Must be called before the {@link #setUp()} method.
*/
protected void disableJMX() {
System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
}
/**
* Enables the JMX agent. Must be called before the {@link #setUp()} method.
*/
protected void enableJMX() {
System.setProperty(JmxSystemPropertyKeys.DISABLED, "false");
}
/**
* Single step debugs and Camel invokes this method before entering the given processor
*/
protected void debugBefore(Exchange exchange, Processor processor, ProcessorDefinition<?> definition,
String id, String label) {
}
/**
* Single step debugs and Camel invokes this method after processing the given processor
*/
protected void debugAfter(Exchange exchange, Processor processor, ProcessorDefinition<?> definition,
String id, String label, long timeTaken) {
}
/**
* To easily debug by overriding the <tt>debugBefore</tt> and <tt>debugAfter</tt> methods.
*/
private class DebugBreakpoint extends BreakpointSupport {
@Override
public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) {
CamelTestSupport.this.debugBefore(exchange, processor, definition, definition.getId(), definition.getLabel());
}
@Override
public void afterProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, long timeTaken) {
CamelTestSupport.this.debugAfter(exchange, processor, definition, definition.getId(), definition.getLabel(), timeTaken);
}
}
}