Processor adding to Camel RouteBuilder - apache-camel

I have 2 different styles of routes that I would like to understand. I hope someone can help.
Style #1.
#Component
public class Route1 extends RouteBuilder {
...
from(RouteName1)
.process(new Processor1())
...
}
public class Processor1 implements Processor {
...
#Override
public void process(Exchange exchange){
..
FileOperations.moveFile(String src, String dest);
}
}
public class FileOperations{
public static void moveFile(..) {
// issue #1
org.apache.camel.util.FileUtil.renameFile(src, dest, true);
File fileToCheck = new File(dest);
java.nio.file.Files.exists(fileToCheck.toPath())
}
Style #2
#Component
public class Route2 extends RouteBuilder {
...
from(RouteName2)
.process("Processor2")
...
}
#Component("Processor2")
public class Processor2 implements Processor {
...
#Override
public void process(Exchange exchange) {
org.apache.camel.util.FileUtil.renameFile(..);
}
}
The issue I am currently facing is that issue #1 renameFile is not reliable. often renameFile returned true and Files.exists check return true incorrectly while the file is not in the destination directory.
So far, Style #2 didn't face that issue.
The way to config route with processor are different. Is it somehow causing this issue? Or anything I miss here?
dependencies
camel-spring-boot-starter:3.9.0...
camel-jdbc, sql,support:3.9.0
spring-boot-starter-xxx: 2.7.1
Thank you!

Related

Unit test Apache Camel specific routes by filtering (Model#setRouteFilter)

How to include only certain routes in my unit test. For example, how do I enable only my-translation-route.
public class TestRoute extends RouteBuilder {
#Override
public void configure() {
from("ftp://my-ftp-server:21/messages")
.routeId("my-inbound-route")
.to("direct:my-translation-route");
from("direct:my-translation-route")
.routeId("my-translation-route")
.bean(MyBean.class)
.to("direct:my-outbound-route");
from ("direct:my-outbound-route")
.routeId("my-translation-route")
.to("http://my-http-server:8080/messages");
}
}
I tried with Model#filterRoutes but this did not work. All routes were loaded.
class TestRouteTest extends CamelTestSupport {
#Override
protected RoutesBuilder createRouteBuilder() {
return new TestRoute();
}
#Override
public boolean isUseAdviceWith() {
return true;
}
#Test
void testIfItWorks() throws Exception {
context.setRouteFilterPattern("my-translation-route", null);
AdviceWith.adviceWith(context, "my-translation-route", a -> {
a.mockEndpointsAndSkip("direct:my-outbound-route");
});
context.start();
getMockEndpoint("mock:direct:my-outbound-route").expectedBodyReceived().expression(constant("Hahaha! 42"));
template.sendBodyAndHeaders("direct:my-translation-route", "42", null);
assertMockEndpointsSatisfied();
}
}
I got it working with the override of CamelTestSupport#getRouteFilterIncludePattern, e.g.:
#Override
public String getRouteFilterIncludePattern() {
return "direct:my-translation-route";
}
But then this is set for all tests in this test class.
Possible (stupid) solution : set a conditional auto startup for your routes, whose value depends on a (Camel or JVM) property that you can set with a particular value during the unit tests:
public class TestRoute extends RouteBuilder {
#PropertyInject(name="productionMode", defaultValue="true")
private boolean productionMode;
#Override
public void configure() {
from("ftp://my-ftp-server:21/messages")
...
.autoStartUp(productionMode); // <=here
}
}
There are various ways to override properties during your tests. See https://camel.apache.org/components/3.17.x/properties-component.html

JMS property breadcrumbId

How can I put breadcrumb that I have generated into this property? for example I've this class:
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
String uuid = getContext().getUuidGenerator().generateUuid();
from("someRoute")to("activemq:queue:someQueue");
}
}
and my question is. How can i put uuid as jms property breadcrumbId?
This should do it
#Override
public void configure() throws Exception {
String uuid = getContext().getUuidGenerator().generateUuid();
from("someRoute").setHeader(Exchange.BREADCRUMB_ID, simple(uuid)).to("activemq:queue:someQueue");
}

Camel bean component invokes cached instance of #Named / #Dependent bean

In our application we are using Apache Camel with camel-cdi component in JBoss EAP 7.1 environment. After upgrade of Apache Camel to actual version the application started to behave incorrectly in parallel execution.
I have found, that bean component invokes always the same instance. From my understanding, bean with #Dependent scope should be always fresh instance for every CDI lookup.
I have tried endpoint parameter cache=false, which should be default, but the behavior stays the same. Also tried to specify #Dependent, which should be default too.
Attaching MCVE, which fails on Apache Camel 2.20.0 and newer. Works well with 2.19.5 and older. Full reproducible project on Github.
#ApplicationScoped
#Startup
#ContextName("cdi-context")
public class MainRouteBuilder extends RouteBuilder {
public void configure() throws Exception {
from("timer:test")
.to("bean:someDependentBean?cache=false");
}
}
#Named
//#Dependent //Dependent is default
public class SomeDependentBean implements Processor {
private int numOfInvocations = 0;
private static Logger log = LoggerFactory.getLogger(SomeDependentBean.class);
public void process(Exchange exchange) throws Exception {
log.info("This is: "+toString());
numOfInvocations++;
if (numOfInvocations!=1){
throw new IllegalStateException(numOfInvocations+"!=1");
} else {
log.info("OK");
}
}
}
Is there anything I can do in our application to change this behavior and use actual version of Apache Camel?
EDIT:
Removing tags camel-cdi and jboss-weld. I have created unit test, to simulate this situation without dependencies to camel-cdi and Weld. This test contains assertion to test JndiRegistry#lookup, which returns correct instance. According this test I believe, the issue is in bean component itself. Fails with version >=2.20.0 and passes with <=2.19.5
public class CamelDependentTest extends CamelTestSupport {
private Context context;
private JndiRegistry registry;
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:in")
.to("bean:something?cache=false");
}
};
}
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry registry = super.createRegistry();
registry.bind("something", new SomeDependentBean());
this.context = registry.getContext();
this.registry = registry;
return registry;
}
#Test
public void testFreshBeanInContext() throws Exception{
SomeDependentBean originalInstance = registry.lookup("something", SomeDependentBean.class);
template.sendBody("direct:in",null);
context.unbind("something");
context.bind("something", new SomeDependentBean()); //Bind new instance to Context
Assert.assertNotSame(registry.lookup("something"), originalInstance); //Passes, the issue is not in JndiRegistry.
template.sendBody("direct:in",null); //fails, uses cached instance of SameDependentBean
}
}
According CAMEL-12610 is Processor supposed to be singleton scope. This behavior was introduced in version 2.20.0. Do not implement Processor interface, instead annotate invokable method as #Handler.
Replace
#Named
public class SomeDependentBean implements Processor {
public void process(Exchange exchange) throws Exception {
}
}
with
#Named
public class SomeDependentBean {
#Handler
public void process(Exchange exchange) throws Exception {
}
}
If you cannot afford that as me, because it is breaking behavior for our app extensions, I have implemented simple component. This component have no caching and allows to invoke Processor directly from registry.
CdiEndpoint class
public class CdiEndpoint extends ProcessorEndpoint {
private String beanName;
protected CdiEndpoint(String endpointUri, Component component) {
super(endpointUri, component);
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
#Override
protected void onExchange(Exchange exchange) throws Exception {
Object target = getCamelContext().getRegistry().lookupByName(beanName);
Processor processor = getCamelContext().getTypeConverter().tryConvertTo(Processor.class, target);
if (processor != null){
processor.process(exchange);
} else {
throw new RuntimeException("CDI bean "+beanName+" not found");
}
}
}
CdiComponent class
public class CdiComponent extends DefaultComponent {
#Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
CdiEndpoint endpoint = new CdiEndpoint(uri, this);
endpoint.setBeanName(remaining);
return endpoint;
}
}
Usage
public void configure() throws Exception {
getContext().addComponent("cdi", new CdiComponent());
from("direct:in")
.to("cdi:something");
}

Camel error thrown while working with interceptSendToEndpoint

While working with the interceptSendToEndpoint, below route throws org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: Endpoint[direct://result]. Exchange[Message: ]
How could I resolve it? Thanks in advance.
public class SampleRouteTest extends CamelTestSupport {
#Test
public void test() {
String expectedBody = "<matched/>";
template.sendBodyAndHeader("direct:start", expectedBody, "foo", "bar");
}
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
interceptSendToEndpoint("direct:result").process(exchange -> System.out.println("intercepted"));
from("direct:start").to("direct:result").process(exchange -> System.out.println("after"));
}
};
}
}
You need a consumer on "direct:result", eg a route with
from("direct:result")
.to("log:result")
Or something. Or instead of direct use a mock / seda or other component.
The direct component is for direct method invocation, eg there must be a link between to->from

RecipientList Apache Camel EIP

I'm trying to use the RecipientList pattern in Camel but I think I may be missing the point. The following code only displays one entry to the screen:
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
from("direct:start").recipientList(bean(MyBean.class, "buildEndpoint"))
.streaming()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getExchangeId());
}
});
}
};
}
public static class MyBean {
public static String[] buildEndpoint() {
return new String[] { "exec:ls?args=-la", "exec:find?args=."};
}
}
I also tried just returning a comma-delimited string from the buildEndpoint() method and using tokenize(",") in the expression of the recipientList() component definition but I still got the same result. What am I missing?
That is expected, the recipient list sends a copy of the same message to X recipients. The processor you do afterwards is doing after the recipient lists is done, and therefore is only executed once.

Resources