Why isn't Camel route test resolving placeholder endpoint? - apache-camel

I have a simple Camel route test class that I have expanded to use a placeholder for an endpoint.
import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
import java.util.Properties;
public class SimpleTestRoute extends CamelTestSupport {
#EndpointInject(uri = "{{test.route.out}}")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Test
public void test() throws Exception {
}
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start").to("{{test.route.out}}");
}
};
}
#Override
protected CamelContext createCamelContext() throws Exception {
Properties props = new Properties();
props.setProperty("test.route.out", "mock:result");
CamelContext context = super.createCamelContext();
PropertiesComponent pc = context.getComponent("properties", PropertiesComponent.class);
pc.setOverrideProperties(props);
return context;
}
}
However, when I run I get
org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint: {{test.route.out}} due to: Property with key [test.route.out] not found in properties from text: {{test.route.out}}
at ...
Caused by: java.lang.IllegalArgumentException: Property with key [test.route.out] not found in properties from text: {{test.route.out}}
at ...
I have tried various combinations of brace locations without progress. Now I suspect the problem is that the override is not being set but I cannot see what I am doing wrong or how to resolve it.
FWIW, I am running camel 2.13.1

Related

Apache Camel - call simple language without side effect

Is it possible to call an object with the Simple language directly within the route and without side effects? The 2 approaches i've tried are;
.toD("${header.exchangeHelper.inc1()}") //works but fails if there is a return type from the called method
.bean(new Simple("${header.exchangeHelper.inc1()}")) //works but sets the body to false
Neither of which give the ideal solution.
You can store the result to exchange property or header instead. This way you'll keep the original body and get the result from your method in case you need it later. Alternatively you can just call the method using a processor.
These are generally better approaches with Java-DSL for something like this than using simple-language since they benefit from IDE's refactoring tools, error highlighting and many forms of linting.
package com.example;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.camel.Exchange;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
public class ExampleTest extends CamelTestSupport {
ExchangeHelper exchangeHelper = mock(ExchangeHelper.class);
#Test
public void useSetPropertyTest() throws Exception {
MockEndpoint resultMockEndpoint = getMockEndpoint("mock:result");
resultMockEndpoint.expectedMessageCount(1);
resultMockEndpoint.message(0).body().isEqualTo("Hello");
when(exchangeHelper.inc1()).thenReturn(true);
template.sendBodyAndHeader("direct:useSetProperty", "Hello",
"exchangeHelper", exchangeHelper);
verify(exchangeHelper, times(1)).inc1();
resultMockEndpoint.assertIsSatisfied();
}
#Test
public void justUseProcessorTest() throws Exception {
MockEndpoint resultMockEndpoint = getMockEndpoint("mock:result");
resultMockEndpoint.expectedMessageCount(1);
resultMockEndpoint.message(0).body().isEqualTo("Hello");
when(exchangeHelper.inc1()).thenReturn(true);
template.sendBody("direct:justUseProcessor", "Hello");
verify(exchangeHelper, times(1)).inc1();
resultMockEndpoint.assertIsSatisfied();
}
#Test
public void useHeaderFromProcessorTest() throws Exception {
MockEndpoint resultMockEndpoint = getMockEndpoint("mock:result");
resultMockEndpoint.expectedMessageCount(1);
resultMockEndpoint.message(0).body().isEqualTo("Hello");
when(exchangeHelper.inc1()).thenReturn(true);
template.sendBodyAndHeader("direct:useHeaderFromProcessor", "Hello",
"exchangeHelper", exchangeHelper);
verify(exchangeHelper, times(1)).inc1();
resultMockEndpoint.assertIsSatisfied();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder(){
#Override
public void configure() throws Exception {
from("direct:useSetProperty")
.setProperty("result")
.simple("${header.exchangeHelper.inc1()}")
.log("Body: ${body} Result header: ${exchangeProperty.result}")
.to("mock:result")
.removeProperty("result");
from("direct:justUseProcessor")
.process( ex -> { exchangeHelper.inc1(); })
.log("Body: ${body}")
.to("mock:result");
from("direct:useHeaderFromProcessor")
.process( ex -> {
ex.getMessage()
.getHeader("exchangeHelper", ExchangeHelper.class)
.inc1();
})
.log("Body: ${body}")
.to("mock:result");
}
};
}
interface ExchangeHelper {
public boolean inc1();
}
}
Not tried, but why not using the wiretap EIP to issue an extra (and separated!) request to your requestHelper method ?
Something like:
from("direct:demo")
.wireTap("bean:${header.exchangeHelper.inc1()}")
.to("direct:doSomething");
You can simply use Camel Script, something like that:
from("direct:exampleScript")
.script().simple("${header.exchangeHelper.inc1()}")
.log("End")
;

Stopping Camel route when no messages in queue (jms, seda)

I have a Camel route that moves messages from a jms queue to another. That route is by default stopped and it is started by a call to a jetty route using controlBus.
As I need to move the messages on demand, once the source jms queue is empty, I need to disable the "mover" route, so messages that arrive later are not processed until the "mover" route is activated again.
Is there a way to achieve that?
You could try to obtain the queue message count using JmsTemplate and then shutting down the route from another thread using a processor. Downsides might be a dependency to org.springframework.jms.core.JmsTemplate and some gotchas related to it.
package com.example;
import java.util.Collections;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.junit.EmbeddedActiveMQBroker;
import org.apache.camel.CamelContext;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.ServiceStatus;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.jms.core.JmsTemplate;
public class QueueConsumerTests extends CamelTestSupport {
#Rule
public EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker();
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"vm://localhost?broker.persistent=false");
#Test
public void stopesQueueListenerRouteAfterConsumingAllMessages() throws Exception {
MockEndpoint jmsMockEndpoint = getMockEndpoint("mock:jmsMockEndpoint");
jmsMockEndpoint.expectedMessageCount(5);
for (int i = 1; i <= 5; i++) {
template.sendBody("direct:test", "Message " + i);
}
context().getRouteController().startRoute("queueListener");
Thread.sleep(5000);
ServiceStatus routeStatus = context().getRouteStatus("queueListener");
assertEquals(routeStatus, ServiceStatus.Stopped);
jmsMockEndpoint.assertIsSatisfied();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:test")
.to("jms:queue:test");
from("jms:queue:test")
.routeId("queueListener")
.autoStartup(false)
.log("message from queue: ${body}")
.to("mock:jmsMockEndpoint")
.setBody().exchange(e -> {
int messageCount = jmsTemplate.browse("test", (session, browser) -> {
return Collections.list(browser.getEnumeration()).size();
});
return messageCount;
})
.filter(body().isEqualTo(0))
.to("seda:stopPolling")
.end();
from("seda:stopPolling?concurrentConsumers=1&multipleConsumers=false")
.log("stop polling")
.process(e -> e.getContext().getRouteController().stopRoute("queueListener"))
.setProperty("stopped").constant(false)
.loopDoWhile(exchangeProperty("stopped").isEqualTo(false))
.delay(100)
.setProperty("stopped").exchange(e -> {
return e.getContext().getRouteStatus("queueListener").isStopped();
})
.end()
.log("stopped queueListener");
}
};
}
#Override
protected CamelContext createCamelContext() throws Exception {
CamelContext context = new DefaultCamelContext();
JmsComponent jmsComponent = new JmsComponent();
jmsComponent.setConnectionFactory(connectionFactory);
context.addComponent("jms", jmsComponent);
return context;
}
}
Alternative could be to use timer with poll-enrich with timeout and shutdown the route if it results in null body. This however is slower and possibly less robust due to how you need to specify frequency of polling and timeout.
package com.example;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.junit.EmbeddedActiveMQBroker;
import org.apache.camel.CamelContext;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.ServiceStatus;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Rule;
import org.junit.Test;
public class QueueConsumerTests2 extends CamelTestSupport {
#Rule
public EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker();
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"vm://localhost?broker.persistent=false");
#Test
public void stopesQueueListenerRouteAfterConsumingAllMessages() throws Exception {
MockEndpoint jmsMockEndpoint = getMockEndpoint("mock:jmsMockEndpoint");
jmsMockEndpoint.expectedMessageCount(5);
for (int i = 1; i <= 5; i++) {
template.sendBody("direct:test", "Message " + i);
}
context().getRouteController().startRoute("queueListener");
Thread.sleep(10000);
ServiceStatus routeStatus = context().getRouteStatus("queueListener");
assertEquals(routeStatus, ServiceStatus.Stopped);
jmsMockEndpoint.assertIsSatisfied();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:test")
.to("jms:queue:test");
from("timer:moverTimer?period=500")
.routeId("queueListener")
.autoStartup(false)
.pollEnrich("jms:queue:test", 1000)
.choice()
.when(body().isNotNull())
.log("message from queue: ${body}")
.to("mock:jmsMockEndpoint")
.otherwise()
.to("seda:stopPolling")
.end();
from("seda:stopPolling?concurrentConsumers=1&multipleConsumers=false")
.log("stop polling")
.process(e -> e.getContext().getRouteController().stopRoute("queueListener"))
.setProperty("stopped").constant(false)
.loopDoWhile(exchangeProperty("stopped").isEqualTo(false))
.delay(100)
.setProperty("stopped").exchange(e -> {
return e.getContext().getRouteStatus("queueListener").isStopped();
})
.end()
.log("stopped queueListener");
}
};
}
#Override
protected CamelContext createCamelContext() throws Exception {
CamelContext context = new DefaultCamelContext();
JmsComponent jmsComponent = new JmsComponent();
jmsComponent.setConnectionFactory(connectionFactory);
context.addComponent("jms", jmsComponent);
return context;
}
}
Now as a disclaimer haven't tested these patterns thoroughly so there might be some edge cases (and better ways to do this). There's also the fact that Camel documentation uses java.lang.Thread to stop route inside a processor instead of using seda consumer so there might be something in to that.
Use of Thread.sleep in unit test is also quite messy and not something I would recommend unless you just want to quickly experiment on something with camel.
Used Dependencies:
org.apache.camel/camel-core/2.24.2
org.apache.camel/camel-jms/2.24.2
Test scope:
org.apache.camel/camel-test/2.24.2
org.apache.activemq.tooling/activemq-junit/5.16.3
org.apache.activemq/activemq-broker/5.16.3

Camel exchange header lost during test

I'm trying to test the onException(JsonProcessingException.class) route in the following class (please don't mind its name, I've cut some code out for clarity):
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import pl.muni.camel.sample.customer.domain.CustomerData;
import pl.muni.camel.sample.customer.route.processor.CreateCustomerErrorResponseProcessor;
import pl.muni.camel.sample.customer.route.processor.CreateCustomerOkResponseProcessor;
#Component
public class SendCustomerDataToQueueRoute extends RouteBuilder {
#Value("${http.rest.listener.host}")
private String restListenerHost;
#Value("${http.rest.listener.port}")
private int restListenerPort;
#Override
public void configure() {
restConfiguration()
.component("restlet")
.dataFormatProperty("prettyPrint", "true")
.host(restListenerHost)
.port(restListenerPort);
rest("/rest/v1/customer")
.post("/create")
.bindingMode(RestBindingMode.json)
.skipBindingOnErrorCode(false)
.consumes("application/json")
.type(CustomerData.class)
.produces("application/json")
.route().id("acceptCreateCustomerRequest")
.from("direct:acceptRequest")
.to("direct:processRequest");
onException(JsonProcessingException.class)
.handled(true)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400))
.to("direct:processException");
onException(Exception.class)
.handled(true)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500))
.to("direct:processException");
from("direct:processRequest").routeId("processCreateCustomerRequest")
.log("Received customer data: ${body}")
.process(new CreateCustomerOkResponseProcessor()).id("createOkResponse");
from("direct:processException").routeId("processCreateCustomerException")
.log(LoggingLevel.ERROR, "${exception.stacktrace}").id("logExceptionStackTrace")
.process(new CreateCustomerErrorResponseProcessor()).id("createErrorResponse");
}
}
I want to intercept the exchange after createErrorResponse processor and run some assertions on it. So far I've come up with this code, in which I weave in a mock endpoint after direct:processException endpoint:
import java.util.List;
import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
import org.apache.camel.Exchange;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.spring.CamelSpringBootRunner;
import org.apache.camel.test.spring.EnableRouteCoverage;
import org.apache.camel.test.spring.MockEndpointsAndSkip;
import org.apache.camel.test.spring.UseAdviceWith;
import org.assertj.core.api.Assertions;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.annotation.DirtiesContext;
import pl.muni.camel.sample.customer.infrastructure.rest.CreateCustomerResponse;
#UseAdviceWith
#MockEndpointsAndSkip("restlet*")
#EnableRouteCoverage
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#SpringBootTest
#ComponentScan("pl.muni.camel.sample.customer")
#RunWith(CamelSpringBootRunner.class)
public class SendCustomerDataToQueueIntegrationTest {
#Produce
private ProducerTemplate producerTemplate;
#Autowired
private CamelContext context;
#EndpointInject(uri = "mock:error")
private MockEndpoint errorEndpoint;
#Before
public void setUp() throws Exception {
context.getRouteDefinition("processCreateCustomerRequest").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveByToUri("direct:processException")
.after()
.to("mock:error");
}
});
context.start();
}
#After
public void tearDown() throws Exception {
context.stop();
}
#Test
public void shouldReturnHttpStatus400ForInvalidJson() throws InterruptedException {
// given
final String customerDataString = "{\"firstName\": \"aaa\", \"lastname\": \"bbb\"}";
//when
producerTemplate.sendBody("direct:acceptRequest", customerDataString);
//then
errorEndpoint.expectedHeaderReceived(Exchange.HTTP_RESPONSE_CODE, 400);
errorEndpoint.assertIsSatisfied();
final List<Exchange> exchanges = errorEndpoint.getExchanges();
Assertions.assertThat(exchanges).hasSize(1);
final Exchange exchange = exchanges.get(0);
final CreateCustomerResponse response = exchange.getIn().getBody(CreateCustomerResponse.class);
Assertions.assertThat(response.isSuccess()).isFalse();
Assertions.assertThat(response.getErrorMessage()).startsWith("UnrecognizedPropertyException: Unrecognized field \"lastname\"");
}
}
Unfortunately, the Exchange.HTTP_RESPONSE_CODE header somehow disappears during the test and the assertion on errorEndpoint fails. I ran the test with debugger and breakpoint set within CreateCustomerErrorResponseProcessor class and there the header was still available.
Is there another way to set up the test and be able to retrieve the header or could this be a bug?
The URI you are weaving ("direct:processException") in your unit test is attached to a wrong route definition.
It should be:
context.getRouteDefinition("processCreateCustomerException").adviceWith(...)
(and not "processCreateCustomerRequest")

Unit testing log nodes in Apache camel routes

Suppose I have the following camel route:
.from("direct:start")
.log("received ${body} message")
.to("mock:end");
How would you test that the message "received Camel rocks!" message is logged when you send a "Camel rocks!" message to the direct:start endpoint
I would read the written file. Or add a custom appender to the logging system and assert that it received the message.
Or check Camel's internal unit tests.
But what exactly are you trying to achieve?
You are supposed to test your application and not the frameworks you are using.
I would not test the actual logging part, but why not save the data you are interested in a property or header, and then in your unit test assert that the value of that property or header must be such and such?
Here is a way to test this which I admit is a bit too invasive. It would have been much easier if AdviceWithBuilder would have been added something like replaceWith(ProcessDefinition replacement).
Here is the working example:
package com.my.org.some.pkg;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.testng.CamelTestSupport;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.springframework.test.util.ReflectionTestUtils;
import org.testng.annotations.Test;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class CamelLoggingTest extends CamelTestSupport {
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start").id("abc")
.log("received ${body} message")
.to("mock:stop");
}
};
}
#Test
public void shouldLogExpectedMessage() throws Exception {
Logger logger = Mockito.mock(Logger.class);
context.getRouteDefinition("abc").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
ReflectionTestUtils.setField(context.getRouteDefinition("abc")
.getOutputs().get(0), "logger", logger);
}
});
when(logger.isInfoEnabled()).thenReturn(true);
sendBody("direct:start", "Camel rocks!");
verify(logger).info("received Camel rocks! message");
}
}

Camel FTP with pollStrategy fails

I have a standard route with a ftp uri as a consumer endpoint with a pollStrategy defined and added to the registry. However, I am getting the following error:
Caused by: java.lang.IllegalArgumentException: Could not find a suitable setter for property: pollStrategy as there isn't a setter method with same type: java.lang.String nor type conversion possible: No type converter available to convert from type: java.lang.String to the required type: org.apache.camel.spi.PollingConsumerPollStrategy with value #pollingStrategy
at org.apache.camel.util.IntrospectionSupport.setProperty(IntrospectionSupport.java:588)
at org.apache.camel.util.IntrospectionSupport.setProperty(IntrospectionSupport.java:616)
at org.apache.camel.util.IntrospectionSupport.setProperties(IntrospectionSupport.java:473)
at org.apache.camel.util.IntrospectionSupport.setProperties(IntrospectionSupport.java:483)
at org.apache.camel.util.EndpointHelper.setProperties(EndpointHelper.java:255)
at org.apache.camel.impl.DefaultComponent.setProperties(DefaultComponent.java:257)
at org.apache.camel.component.file.GenericFileComponent.createEndpoint(GenericFileComponent.java:67)
at org.apache.camel.component.file.GenericFileComponent.createEndpoint(GenericFileComponent.java:37)
at org.apache.camel.impl.DefaultComponent.createEndpoint(DefaultComponent.java:114)
at org.apache.camel.impl.DefaultCamelContext.getEndpoint(DefaultCamelContext.java:568)
I have tried different combinations but always end up with this error. Can anyone spot what I am missing? My code seems fairly similar to the Camel unit tests I looked at. The route looks like this:
import org.apache.camel.*;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultPollingConsumerPollStrategy;
import org.apache.camel.spi.PollingConsumerPollStrategy;
import org.apache.camel.util.ServiceHelper;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import static org.apache.camel.builder.ProcessorBuilder.setBody;
public class Test extends RouteBuilder {
final CamelContext camelContext = getContext();
final org.apache.camel.impl.SimpleRegistry registry = new org.apache.camel.impl.SimpleRegistry();
final org.apache.camel.impl.CompositeRegistry compositeRegistry = new org.apache.camel.impl.CompositeRegistry();
private final CountDownLatch latch = new CountDownLatch(1);
#Override
public void configure() throws Exception {
ExceptionBuilder.setup(this);
compositeRegistry.addRegistry(camelContext.getRegistry());
compositeRegistry.addRegistry(registry);
((org.apache.camel.impl.DefaultCamelContext) camelContext).setRegistry(compositeRegistry);
registry.put("pollingStrategy", new MyPollStrategy());
from("ftp://user#localhost/receive/in?password=1234&autoCreate=false&startingDirectoryMustExist=true&pollStrategy=#pollingStrategy&fileName=test.csv&consumer.delay=10m")
.convertBodyTo(String.class)
.log(LoggingLevel.INFO, "TEST", "${body} : ${headers}");
}
private class MyPollStrategy implements PollingConsumerPollStrategy {
int maxPolls=3;
public boolean begin(Consumer consumer, Endpoint endpoint) {
return true;
}
public void commit(Consumer consumer, Endpoint endpoint, int polledMessages) {
if (polledMessages > maxPolls) {
maxPolls = polledMessages;
}
latch.countDown();
}
public boolean rollback(Consumer consumer, Endpoint endpoint, int retryCounter, Exception cause) throws Exception {
return false;
}
}
}
Note, if I remove the pollStrategy reference in the uri then everything works.
Ok found the solution..must have had one too many beers when working on this..a bit too obvious.
final CamelContext camelContext = getContext();
final org.apache.camel.impl.SimpleRegistry registry = new org.apache.camel.impl.SimpleRegistry();
final org.apache.camel.impl.CompositeRegistry compositeRegistry = new org.apache.camel.impl.CompositeRegistry();
That part should be in the configure method and not in the class variable declaration part.

Resources