Apache Camel - call simple language without side effect - apache-camel

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")
;

Related

How to throw exceptions from interceptSendToEndpoint handler in camel

I want to simulate HTTP exceptions for testing purposes in an integration test setting. I am using interceptSendToEndpoint. From the handler I can log, modify headers or body, but I can't throw any exception back into the intercepted route. They just get logged and that's it. They are not caught by an onException handler or doTry..doCatch block of the intercepted route where the code is that I actually want to test.
So my handler looks like
interceptSendToEndpoint("undertow:*").when(method("exTest", "enabled"))
.throwException(new Exception(("my Exception")))
Can somebody help me out here? I am still on camel 2.25? Is this different between 2.x and 3.x?
If you're talking about unit testing then you should use adviceWith with weaveByToUri or weaveById to replace the http-endpoint with your custom logic that you can then configure to throw the exception.
As far as I understand interceptSendToEndpoint isn't really intended to be used like this in unit testing.
Example:
package com.example;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.AdviceWithRouteBuilder;
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 {
#Test
public void testHttpErrorHandling() throws Exception {
context.getRouteDefinition("testRoute").adviceWith(context, new AdviceWithRouteBuilder(){
#Override
public void configure() throws Exception {
weaveByToUri("http*")
.replace()
.throwException(Exception.class, "Test Exception");
weaveById("logHttpExceptionEndpoint")
.after()
.to("mock:result");
}
});
MockEndpoint resultMockEndpoint = getMockEndpoint("mock:result");
resultMockEndpoint.expectedMessageCount(1);
resultMockEndpoint.message(0).exchangeProperty("CamelExceptionCaught")
.isInstanceOf(Exception.class);
startCamelContext();
template.sendBody("direct:test", "");
resultMockEndpoint.assertIsSatisfied();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder(){
#Override
public void configure() throws Exception {
from("direct:test")
.routeId(("testRoute"))
.onException(Exception.class)
.log("caught exception.").id("logHttpExceptionEndpoint")
.handled(true)
.end()
.to("http:somewebsite.com")
.log("Hello from test");
}
};
}
#Override
public boolean isUseAdviceWith() {
return true;
}
}
As for integration testing you can use a stub or something like mockoon to simulate the web-services to see how they handle different kinds of exceptions outside unit tests.

Modify the message body, or other wise modify some data in a camel route

What is the best way to make a small modification to some data in a Camel route?
I'm pulling in a BSON document from Mongo. I need to use a timestamp from it in an http call, but I need to convert it from milliseconds to seconds.
I tried setting a header.
.setHeader("test").jsonpath("$.startTime")
Which lets me add the timestamp to the URL with a Simple expression.
.toD("https://test.com/api/markets?resolution=60&start_time=${headers.test}")
But I can't find a way to modify the value of the header.
I also tried using a process
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
DocumentContext message = JsonPath.parse(exchange.getMessage().getBody());
String time = message.read("$.startTime").toString();
time = "111100000";
// do something with the payload and/or exchange here
//exchange.getIn().setBody("Changed body");
}
})
But here the exchange isn't passed back out. I based this on how I used an enrich EIP, with an aggregation strategy that returned an Exchange with the changes I made. This Process doesn't seem to work that way.
You can modify body, header or property using Lambda, processor or a bean. With processor you need to use Message.setHeader method to modify the value of the header at least for value types and strings. Bean methods receive body value by default so if you want to pass in header you'll need to specify it using simple language.
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
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 SetHeaderTest extends CamelTestSupport {
#Test
public void testGreeting() throws Exception {
MockEndpoint resultMockEndpoint = getMockEndpoint("mock:result");
resultMockEndpoint.expectedMessageCount(3);
template.sendBodyAndHeader("direct:modifyGreetingLambda",
null, "greeting", "Hello");
template.sendBodyAndHeader("direct:modifyGreetingProcessor",
null, "greeting", "Hello");
template.sendBodyAndHeader("direct:modifyGreetingBean",
null, "greeting", "Hello");
resultMockEndpoint.assertIsSatisfied();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder(){
#Override
public void configure() throws Exception {
from("direct:modifyGreetingLambda")
.routeId("modifyGreetingLambda")
.setHeader("greeting").exchange(exchange -> {
String modifiedGreeting = (String)exchange.getMessage().getHeader("greeting");
modifiedGreeting += " world!";
return modifiedGreeting;
})
.log("${headers.greeting}")
.to("mock:result");
from("direct:modifyGreetingProcessor")
.routeId("modifyGreetingProcessor")
.process(new Processor(){
#Override
public void process(Exchange exchange) throws Exception {
String modifiedGreeting = (String)exchange.getMessage().getHeader("greeting");
modifiedGreeting += " world!";
exchange.getMessage().setHeader("greeting", modifiedGreeting);
}
})
.log("${headers.greeting}")
.to("mock:result");
from("direct:modifyGreetingBean")
.routeId("modifyGreetingBean")
.setHeader("greeting").method(new ModifyGreetingBean(),
"modifyGreeting('${headers.greeting}')")
.log("${headers.greeting}")
.to("mock:result");
}
};
}
public class ModifyGreetingBean {
public String modifyGreeting(String greeting) {
return greeting + " world!";
}
}
}
Aside from these you can also use expression languages like simple or groovy.
In the route you can set the header with the milliseconds value .setHeader("test").jsonpath("$.startTime").
Then in a processor you can retrieve this value:
String milliSecondsValue = (String) exchange.getIn().getHeader("test");
Then you transform the milliSecondsValue to the value you want and you set it back on the exchange:
exchange.getIn().setHeader("test", secondsValue);
After that call .toD("https://test.com/api/markets?resolution=60&start_time=${header.test}") and it will use the seconds value

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.

Why isn't Camel route test resolving placeholder endpoint?

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

Resources